Reputation: 125
There is memory leak as B constructor not called after exception. What is best way to handle exception in constructor if that constructor allocates objects of other class and if at some point exception occurs it need to free all previously allocated objects or is there is way to manage this automatically.
#include <iostream>
#include<utility>
using namespace std;
class A
{
private:
int *eleA;
public:
A()
{
eleA = new int;
//*eleA = 0;
cout<<"Constructor A\n";
}
~A()
{
cout<<"Destructor A \n";
delete eleA;
eleA = nullptr;
}
};
class B
{
private:
int *eleB;
public:
B()
{
cout<<"Constructor B\n";
eleB = new int;
//*eleB = 0;
throw -1; //Exception thrown
}
~B()
{
cout<<"Destructor B \n";
delete eleB;
eleB = nullptr;
}
};
class AB
{
private:
A *a_ptr;
B *b_ptr;
public:
AB() : a_ptr(nullptr),b_ptr(nullptr)
{
try{
cout<<"Constructor AB \n";
a_ptr = new A;
b_ptr = new B;
}
catch(int a)
{
cout<<"Exception catched\n";
}
}
~AB()
{
cout<<"Destructor AB \n";
if (a_ptr)
{
delete a_ptr;
a_ptr = nullptr;
}
if (b_ptr)
{
delete b_ptr;
b_ptr = nullptr;
}
}
};
int main()
{
AB *ab = new AB;
delete ab;
return 0;
}
Output:
Constructor AB
Constructor A
Constructor B
Exception catched
Destructor AB
Destructor A
Upvotes: 0
Views: 235
Reputation: 96013
The obvious answer is "use smart pointers". Replace int *eleA;
with std::unique_ptr<int> eleA;
, replace eleA = new int;
with eleA = std::make_unique<int>();
, remove delete eleA;
, and you're done (repeat the same thing for other pointers you have). Such objects will be deleted automatically, so you don't have to worry about leaks.
But it's still good to know a generic solution that you can use to manage arbitrary resources (not only pointers), e.g. various handles from C APIs. That solution is scope guards!
They are not in standard C++, but implementing them is easy, so there are countless libraries that provide them. You can write them yourself; an example implementation is at the end of the answer.
Here's how you use them:
int main()
{
int *ptr = new int;
FINALLY( delete ptr; )
// Do something with `ptr` here.
}
FINALLY()
is a macro that creates a scope guard. The idea is that delete ptr;
is not executed immediately, but rather is executed when control leaves the current scope, i.e. either reaches the closing brace }
, or because of an exception. This means that you no longer need to worry about deleting ptr
, regardless of any exceptions.
How do we apply this to your constructor? We make a different macro, that only runs code if you leave the scope because of an exception:
B()
{
cout<<"Constructor B\n";
eleB = new int;
FINALLY_ON_THROW( delete eleB; )
*eleB = 0;
throw -1; //Exception thrown
}
This generalizes to any number of resources. You just put such a scope guard after creating each one of them.
Example scope guard implementation:
#include <exception>
#include <utility>
namespace Macro
{
template <typename T> class ScopeGuard
{
T func;
public:
ScopeGuard(T &&func) : func(std::move(func)) {}
ScopeGuard(const ScopeGuard &) = delete;
ScopeGuard &operator=(const ScopeGuard &) = delete;
~ScopeGuard() {func();}
};
// Same as `ScopeGuard`, but can throw.
template <typename T> class ScopeGuardExc
{
T func;
public:
ScopeGuardExc(T &&func) : func(std::move(func)) {}
ScopeGuardExc(const ScopeGuardExc &) = delete;
ScopeGuardExc &operator=(const ScopeGuardExc &) = delete;
~ScopeGuardExc() noexcept(false) {func();}
};
}
#define FINALLY_impl_cat(a, b) FINALLY_impl_cat_(a, b)
#define FINALLY_impl_cat_(a, b) a##b
#define FINALLY(...) \
::Macro::ScopeGuard FINALLY_impl_cat(_finally_object_,__LINE__) \
([&]() -> void { __VA_ARGS__ });
#define FINALLY_ON_THROW(...) \
::Macro::ScopeGuard FINALLY_impl_cat(_finally_object_,__LINE__) \
([&, _finally_exc_depth_ = ::std::uncaught_exceptions()]() -> void { if (::std::uncaught_exceptions() > _finally_exc_depth_) {__VA_ARGS__} });
#define FINALLY_ON_SUCCESS(...) \
::Macro::ScopeGuardExc FINALLY_impl_cat(_finally_object_,__LINE__) \
([&, _finally_exc_depth_ = ::std::uncaught_exceptions()]() -> void { if (::std::uncaught_exceptions() <= _finally_exc_depth_) {__VA_ARGS__} });
Upvotes: 2