Reputation: 8018
Let's suppose the following simple example:
#include <iostream>
struct foo {
~foo() {
std::cout << "~foo()" << std::endl;
}
};
struct bar {
foo x;
bar() : x() {
throw -1;
}
~bar() {
std::cout << "~bar()" << std::endl;
}
};
struct baz {
~baz() {
std::cout << "~baz()" << std::endl;
}
};
int main() {
try {
baz y;
bar z;
} // Destructor is called for everything fully constructed up to here?
catch(...) {
}
}
The output is
~foo()
~baz()
So obviously bar
's destructor isn't called.
What does that mean for any kind of resource allocation which is intended to be released in bar
's destructor?
E.g.
struct bar {
CostlyResource cr;
bar() {
cr.Open(); // Aquire resource
// something else throws ...
throw -1;
}
~bar() {
if(cr.IsOpen()) {
cr.Release(); // Free resource
}
}
};
For sake of exception safe implementation what can I do to ensure that bar
's resource member is properly released?
Upvotes: 0
Views: 105
Reputation: 141598
An object's lifetime doesn't begin until the constructor is completed. If you throw an exception from the constructor, then that object's destructor will not be called. Of course, any subobjects that have already been constructed will then be destructed, in reverse order of construction, as you saw in your first example with ~foo()
appearing.
The code in your second example is not exception-safe. CostlyResource
is poorly designed - its own destructor should free the resource. Then your code would be correct.
If you have to work with an existing class that doesn't clean itself up properly then you should make a wrapper that does, e.g.:
struct SafeCostlyResource : CostlyResource
{
~SafeCostlyResource()
{
if (IsOpen())
Release();
}
};
and use that as the type of cr
.
(note - this is illustrative pseudocode, there are several ways to approach this problem).
Upvotes: 1
Reputation: 8018
For sake of exception safe implementation what can I do to ensure that bar's resource member is properly released?
You can catch
, handle and rethrow an anonymous excption in your constructor:
struct bar {
CostlyResource cr;
bar() {
try { // Wrap the whole constructor body with try/catch
cr.Open(); // Aquire resource
// something else throws ...
throw -1;
}
catch(...) { // Catch anonymously
releaseResources(); // Release the resources
throw; // Rethrow the caught exception
}
}
~bar() {
releaseResources(); // Reuse the code ro release resources
}
private:
void releaseResources() {
if(cr.IsOpen()) {
cr.Release(); // Free resource
}
}
};
See the full example code here please.
As that's a problem frequently asked for dynamic memory allocation done in constructors like
class MyClass {
TypeA* typeAArray;
TypeB* typeBArray;
public:
MyClass() {
typeAAArray = new TypeA[50];
// Something else might throw here
typeBAArray = new TypeB[100];
}
~MyClass() {
delete[] typeAAArray;
delete[] typeBAArray;
}
};
the easiest way to come over this is either to use an appropriate container (e.g. std::vector<TypeA>
, std::vector<TypeB>
), or a smart pointer (e.g. std::unique_ptr<TypeA[50]>
).
Upvotes: 2