Home Learning Chapter 3: Resource Management Resource Management
Chapter 3: Resource Management Resource Management

Chapter 3: Resource Management Resource Management

0
1

Resource Management

A resource is something that, once you’re done using it, you need to
return to the system. If you don’t, bad things happen. In C++ pro
grams, the most commonly used resource is dynamically allocated
memory (if you allocate memory and never deallocate it, you’ve got a
memory leak), but memory is only one of many resources you must
manage. Other common resources include file descriptors, mutex
locks, fonts and brushes in graphical user interfaces (GUIs), database
connections, and network sockets. Regardless of the resource, it’s
important that it be released when you’re finished with it.
Trying to ensure this by hand is difficult under any conditions, but
when you consider exceptions, functions with multiple return paths,
and maintenance programmers modifying software without fully com
prehending the impact of their changes, it becomes clear that ad hoc
ways of dealing with resource management aren’t sufficient.
This chapter begins with a straightforward object-based approach to
resource management built on C++’s support for constructors,
destructors, and copying operations. Experience has shown that dis
ciplined adherence to this approach can all but eliminate resource
management problems. The chapter then moves on to Items dedicated
specifically to memory management. These latter Items complement
the more general Items that come earlier, because objects that man
age memory have to know how to do it properly. Item 13: Use objects to manage resources.
Suppose we’re working with a library for modeling investments (e.g.,
stocks, bonds, etc.), where the various investment types inherit from a
root class Investment:
class Investment { … }; // root class of hierarchy of
// investment types

Further suppose that the way the library provides us with specific
Investment objects is through a factory function (see Item 7):
Investment* createInvestment(); // return ptr to dynamically allocated
// object in the Investment hierarchy;
// the caller must delete it
// (parameters omitted for simplicity)
As the comment indicates, callers of createInvestment are responsible
for deleting the object that function returns when they are done with
it. Consider, then, a function f written to fulfill this obligation:
void f()
{
Investment *pInv = createInvestment(); // call factory function
… // use pInv
delete pInv; // release object
}
This looks okay, but there are several ways f could fail to delete the
investment object it gets from createInvestment. There might be a premature return statement somewhere inside the “…” part of the function. If such a return were executed, control would never reach the
delete statement. A similar situation would arise if the uses of createInvestment and delete were in a loop, and the loop was prematurely
exited by a break or goto statement. Finally, some statement inside the
“…” might throw an exception. If so, control would again not get to the
delete. Regardless of how the delete were to be skipped, we’d leak not
only the memory containing the investment object but also any
resources held by that object.
Of course, careful programming could prevent these kinds of errors,
but think about how the code might change over time. As the software
gets maintained, somebody might add a return or continue statement
without fully grasping the repercussions on the rest of the function’s
resource management strategy. Even worse, the “…” part of f might
call a function that never used to throw an exception but suddenly
starts doing so after it has been “improved.” Relying on f always getting to its delete statement simply isn’t viable.
To make sure that the resource returned by createInvestment is always
released, we need to put that resource inside an object whose destructor will automatically release the resource when control leaves f. In
fact, that’s half the idea behind this Item: by putting resources inside
objects, we can rely on C++’s automatic destructor invocation to make
sure that the resources are released. (We’ll discuss the other half of
the idea in a moment.)
ptg7544714
Resource Management Item 13 63
Many resources are dynamically allocated on the heap, are used only
within a single block or function, and should be released when control
leaves that block or function. The standard library’s auto_ptr is tailormade for this kind of situation. auto_ptr is a pointer-like object (a
smart pointer) whose destructor automatically calls delete on what it
points to. Here’s how to use auto_ptr to prevent f’s potential resource
leak:
void f()
{
std::auto_ptr<Investment> pInv(createInvestment()); // call factory
// function
… // use pInv as
// before
} // automatically
// delete pInv via
// auto_ptr’s dtor
This simple example demonstrates the two critical aspects of using
objects to manage resources:
■ Resources are acquired and immediately turned over to resource-managing objects. Above, the resource returned by createInvestment is used to initialize the auto_ptr that will manage it. In
fact, the idea of using objects to manage resources is often called
Resource Acquisition Is Initialization (RAII), because it’s so common
to acquire a resource and initialize a resource-managing object in
the same statement. Sometimes acquired resources are assigned
to resource-managing objects instead of initializing them, but either way, every resource is immediately turned over to a resourcemanaging object at the time the resource is acquired.
■ Resource-managing objects use their destructors to ensure
that resources are released. Because destructors are called automatically when an object is destroyed (e.g., when an object goes
out of scope), resources are correctly released, regardless of how
control leaves a block. Things can get tricky when the act of releasing resources can lead to exceptions being thrown, but that’s a
matter addressed by Item 8, so we’ll not worry about it here.
Because an auto_ptr automatically deletes what it points to when the
auto_ptr is destroyed, it’s important that there never be more than one
auto_ptr pointing to an object. If there were, the object would be
deleted more than once, and that would put your program on the fast
track to undefined behavior. To prevent such problems, auto_ptrs have
an unusual characteristic: copying them (via copy constructor or copy
ptg7544714
64 Item 13 Chapter 3
assignment operator) sets them to null, and the copying pointer
assumes sole ownership of the resource!
std::auto_ptr<Investment> // pInv1 points to the
pInv1(createInvestment()); // object returned from
// createInvestment
std::auto_ptr<Investment> pInv2(pInv1); // pInv2 now points to the
// object; pInv1 is now null
pInv1 = pInv2; // now pInv1 points to the
// object, and pInv2 is null
This odd copying behavior, plus the underlying requirement that
resources managed by auto_ptrs must never have more than one
auto_ptr pointing to them, means that auto_ptrs aren’t the best way to
manage all dynamically allocated resources. For example, STL containers require that their contents exhibit “normal” copying behavior,
so containers of auto_ptr aren’t allowed.
An alternative to auto_ptr is a reference-counting smart pointer (RCSP).
An RCSP is a smart pointer that keeps track of how many objects
point to a particular resource and automatically deletes the resource
when nobody is pointing to it any longer. As such, RCSPs offer behavior that is similar to that of garbage collection. Unlike garbage collection, however, RCSPs can’t break cycles of references (e.g., two
otherwise unused objects that point to one another).
TR1’s tr1::shared_ptr (see Item 54) is an RCSP, so you could write f this
way:
void f()
{

std::tr1::shared_ptr<Investment>
pInv(createInvestment()); // call factory function
… // use pInv as before
} // automatically delete
// pInv via shared_ptr’s dtor
This code looks almost the same as that employing auto_ptr, but copying shared_ptrs behaves much more naturally:
void f()
{

std::tr1::shared_ptr<Investment> // pInv1 points to the
pInv1(createInvestment()); // object returned from
// createInvestment
ptg7544714
Resource Management Item 13 65
std::tr1::shared_ptr<Investment> // both pInv1 and pInv2 now
pInv2(pInv1); // point to the object
pInv1 = pInv2; // ditto — nothing has
// changed

} // pInv1 and pInv2 are
// destroyed, and the
// object they point to is
// automatically deleted
Because copying tr1::shared_ptrs works “as expected,” they can be used
in STL containers and other contexts where auto_ptr’s unorthodox
copying behavior is inappropriate.
Don’t be misled, though. This Item isn’t about auto_ptr, tr1::shared_ptr,
or any other kind of smart pointer. It’s about the importance of using
objects to manage resources. auto_ptr and tr1::shared_ptr are just
examples of objects that do that. (For more information on
tr1:shared_ptr, consult Items 14, 18, and 54.)
Both auto_ptr and tr1::shared_ptr use delete in their destructors, not
delete []. (Item 16 describes the difference.) That means that using
auto_ptr or tr1::shared_ptr with dynamically allocated arrays is a bad
idea, though, regrettably, one that will compile:
std::auto_ptr<std::string> // bad idea! the wrong
aps(new std::string[10]); // delete form will be used
std::tr1::shared_ptr<int> spi(new int[1024]); // same problem
You may be surprised to discover that there is nothing like auto_ptr or
tr1::shared_ptr for dynamically allocated arrays in C++, not even in
TR1. That’s because vector and string can almost always replace
dynamically allocated arrays. If you still think it would be nice to have
auto_ptr- and tr1::shared_ptr-like classes for arrays, look to Boost (see
Item 55). There you’ll be pleased to find the boost::scoped_array and
boost::shared_array classes that offer the behavior you’re looking for.

Leave a Reply