Resource ownership C++
As I’ve have explained earlier one the strengths of using auto_ptr
is that you can take compiler enforced preventive action during method
design against possible leaks generated by ignorant callers. Obviously
this is a good thing since code that allows for misuse will cause more
troubles than one that does not. In this post I will explain how to
write object construction and initialization code that minimizes chance
for resource leaks without imposing special requirements on caller.
I prefer to interpret the often quoted guideline:
allocate resources in constructor and deallocate them in constructor
as:
- each resource should be owned at all time by one object,
- an object should not own directly more than one resource.
Owned
means that whatever execution path should we follow, object will
perform cleanup or it will transfer this responsibility explicitly to
another owning object. Resource transfer should happen via auto_ptr or other suitable smart pointer. As for construction let’s see some code:
class SomeClass
{
public:
SomeClass::SomeClass()
: myObject(new Object())
mySecondObject(NULL)
{
//...
mySecondObject = auto_ptr<Object>(new Object());
//...
}
//...
private:
auto_ptr<Object> myObject;
auto_ptr<Object> mySecondObject;
Object myThirdObject;
};
Forgetting the shabby naming it is easy to check that skeleton of SomeClass
complies with both rules. We will do this by following up all members
for possible leaks. Note that we don’t need to search any local
variables used in omitted SomeClass methods they where already checked. Non pointer-like members as myThirdObject will have their destructor called automatically by language mechanics. This will also happen if an exception is thrown inside SomeClass constructor body. Similarly smart pointer like myObject and mySecondObject are also safe regardless of execution path.
Though ultimately SomeClass owns multiple resources, from its two pointer members plus any dynamical memory owned by the objects of class Object
(name clarity problem strikes again), none of them is owned directly.
Problem with more than one direct owned resources is that if you fail
to allocate the first, you have to ensure that second is released. For
example if myObject would be a bare pointer, we could not
rely on automatic cleanup from an exception in constructor body. This
gets even worse for each additional resource and you will and up with a
clumsy object construction code. In some sense presence of an exception
path means that a resource is present.
Avoiding naked pointers will save you headaches and considering existing alternatives this can be archived easily: when member objects life time is determined by object lifetime, you can opt for simple object aggregation.
class Foo
{
public:
Foo()
: myBar(NULL)
{
myBar = new Bar();
myBar->risky();
//...
}
Foo()
{
delete myBar;
}
//...
private:
Bar* myBar;
};
Compared to the simpler:
class Foo
{
public:
Foo()
{
myBar.risky();
//...
}
//...
private:
Bar myBar;
};
If members are not constructed within the object (they are provided
from outside) but destruction is determined by it, you can use an
encapsulation similar to std::auto_ptr.
class Foo
{
public:
Foo(Bar* bar)
: myBar(bar)
{
myBar->risky();
//...
}
Foo()
{
delete myBar;
}
private:
Bar* myBar;
};
again the simpler:
class Foo
{
public:
Foo(Bar* bar)
: myBar(bar)
{
myBar->risky();
//...
}
private:
auto_ptr<Bar> myBar;
};
If object lifetime is shorter that those of of its member objects you could use references instead and state explicitly “I’m not an owner”
, references can not be deleted unless using a weird eye popper syntax.
class Foo
{
public:
Foo(Bar* bar)
: myBar(bar)
{
//...
}
//...
private:
Bar* myBar;
};
vs the safer:
class Foo
{
public:
Foo(Bar& bar)
: myBar(bar)
{
//...
}
//...
private:
Bar& myBar;
};
When lifetime cannot be determined boost::shared_ptr like behavior can help.
Why is it safer to write object construction code like on the right side? Because language mechanics will perform cleanup automatically regardless of execution path. And as a bonus you will have less code with fewer chances for errors.
