Reputation: 3377
I have Java experience and recently am doing some C++ coding. My question is that if I have class A, in which I have to instantiate class B and class C as two of the member variables of A.
If in the constructor of A, should I assume that allocations of class B and C never fail, and handle the bad allocation exception in the destructor of A?
If I don't make that assumption, meaning that I add some try catch block to catch bad_alloc of class B and class C, then if the allocation exception occurs, should I do clean up in the constructor of A?
What are the recommended practices? If "new" generates a bad allocation, what value does the pointer carry?
Upvotes: 2
Views: 4105
Reputation: 11657
Using RAII idiom prevent you (and your code) from any headache:
class A {};
class B {
public:
B() {throw std::exception();}
};
class C {
public:
C() {
a.reset(new A());
b.reset(new B()); //failes with std::exception
//after b ctor throws exception, all destructor for fully contructed objects would be called,
//i.e. a destructor would be called automaticly
}
~C() {
//destructor is empty, because RAII all the stuff for us
}
private:
std::auto_ptr<A> a;
std::auto_ptr<B> b;
};
And I think you should read greate article "Constructor Exceptions in C++, C#, and Java" by Herb Sutter, where Herb describes exactly the same problem.
Upvotes: 1
Reputation: 208406
The answer by @GMan is quite complete. To be a little more concrete as to your particular question, if an exception is thrown during construction (at any point), all fully constructed objects will have their destructor called. Partially constructed objects will not have their destructors called. Now the long story...
The implications are: if every individual resource (not bundle of them) is managed by its own RAII mechanism you are fine. The first object will be created and the resource handled by the RAII mechanism, the second object will then be created and so on. At any given point if an exception is thrown, all resources that were acquired will already be managed inside a fully constructed RAII holder that will free them.
struct willthrow {
willthrow() { throw std::exception(); }
};
class bad {
public:
bad() : a( new int(0) ), b() {}
~bad() { delete a; }
private:
int * a;
willthrow b;
};
class good {
public:
good() : a( new int(0) ), b() {}
private:
std::auto_ptr<int> a;
willthrow b;
}
In the bad
case, when the second element is being constructed an exception is raised. At that point a
is holding a resource (allocated memory), but it is doing directly. The destructor of bad
will not be called as it was not fully constructed, so even if it may look in the code that a
will be freed in ~bad()
, that will never be called and you have a memory leak.
In the good
case, memory is allocated and passed into the a
which is an auto_ptr
. The a
subobject is fully constructed before initialization of b
starts. When the b
constructor throws, the compiler will call ~a
(remember: the subobject is fully constructed) and that will in turn free the allocated memory. Also note that the good
class has no destructor: all resources are already managed by subobjects, so there is no need to manually perform any such operation.
Upvotes: 4
Reputation: 504073
If an exception is thrown during the construction of A, your destructor will not be called.
Obviously the solution depends on what you're doing, but ideally you won't have to do any cleaning up. You should utilize RAII, and your class members should clean-up themselves.
That is, don't use any pointers raw; wrap them up and let the wrapper take care of it. Surprise! C++ programmers hate memory management just like you. We like to wrap it up and forget about it.
If you truly need to, though, I think this is common:
struct foo
{
int* i;
some_member_that_could_throw crap;
foo() // do *not* new i! if the second member throws, memory is leaked.
{ // rather:
// okay we made it, the other member must have initialized
i = new int;
}
};
Concerning your pointer, it's value remains unchanged. When new
throws an exception (for whatever reason), the stack is unwound. The rest of the expression is abandoned.
Here's how exceptions and object creation will work. It's a recursive process, because each member or base class will in turn follow this list. Fundamental types have no constructors; this is the base case for the recursion.
Obviously, if item 1 fails there isn't any cleaning up for us to do, as none of our members have been initialized. We're good there.
Two is different. If at any point one of those fails to construct , the initialized members so far will be destructed, then the constructor will stop progress and the exception goes on it's merry way. This is why when you let your members clean up after themselves you have nothing to worry about. The uninitialized have nothing to do, and the initialized are going to have their destructors run, where cleanup occurs.
Three even more so. now that your objects are fully initialized, you're guaranteed they will all have their destructors run. Again, wrap things up and you have nothing to worry about. However if you have a raw pointer lying around, this is the time for a try/catch block:
try
{
// some code
}
catch (..) // catch whatever
{
delete myrawPointer; // stop the leak!
throw; // and let the exception continue
}
It's much messier to write exception-safe code without RAII.
Upvotes: 7
Reputation: 7334
Simple part first: If new fails and throws std::bad_alloc, nothing is assigned to the pointer. You can use new (nothrow) in which case it would return null instead of throwing, but that's uncommon.
I don't think you should catch bad_alloc unless you can do something about it. If your class A fails to allocate B and C, how are you going to recover from that? It might be possible in some rare cases, but in almost all situations it would be better not to catch the exception so that the construction of A fails as a whole.
If you try to catch every bad_alloc which might occur (but probably won't) then your code is going to rapidly devolve into a mess of try/catch statements and conditionals (every time you try to do things with the B and C members of A, you'd have to check they'd constructed successfully). Much easier IMO to require that B and C were constructed properly in order for A to be.
I'm assuming here that B and C are reasonably small, so it'd be an uncommon occurrence for their construction to fail. If that was more likely (eg. they're trying to allocate 300MB buffers or something) then you might approach it differently - but I'm guessing that's not the case :)
Upvotes: 1