July 31, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

Easy C++ Resource Management with shared_ptr

  • August 17, 2009
  • By Nick Wienholt
  • Send Email »
  • More Articles »

Every C and C++ developer knows the pain of tracking down bugs caused by incorrect resource deallocation in which delete is called either too early or not at all. One key motivator for using managed runtimes such as .NET is garbage collection, which largely eliminates the need for developers to write code around resource deallocation manually.

A previous attempt at providing library-based support for addressing this problem in standard C++ resulted in the auto_ptr class, which wrapped a manually-managed resource like heap memory in a stack-based templated class. However, auto_ptr had some problems, one of which was that is supported only a single owner—and it was the owner of the auto_ptr that determined when the delete call was made on the wrapped resource. Ownership of an auto_ptr was typically passed when the copy constructor was called, and this played all sorts of havoc when an attempt was made to use auto_ptr with STL collections, which make heavy use of copy constructors. In addition, the auto_ptr class was not const-friendly, and did not support a user-defined Boolean conversion, which created awkward syntax for null pointer checks in if statements:

auto_ptr<B> pB(new B());
if (pB.get()){}
//if (pB) {} does not compile

The new shared_ptr template class, which is part of the TR1 extensions to C++ and ships with Visual C++ 2008 SP1 and Visual C++ 2010, addresses all the problems with auto_ptr discussed above. The most fundamental difference between the two pointer-wrapping templates is the move from a simple ownership model in auto_ptr to a reference-counting model in shared_ptr. For long-term Visual C++ developers wincing with pain at the mention of reference-counting in COM, the good news is that shared_ptr manages the majority of the reference-counting pain, and there is no need to introduce reference counting into each class that wants to take advantage of shared_ptr. The bad news is that the problem of circular references remains, and developers must show the same degree of caution when setting up shared_ptr relationships that they had to exercise in COM to avoid resource leaks.

The basic use of shared_ptr is much the same as auto_ptr, but with a few of the pet peeves removed. It is also possible to track how many shared_ptr objects are holding onto a pointer through the use of the use_count function:

class A{
public:
 void f() {}
};

shared_ptr<A> pA1(new A());
pA1->f();
if (pA1) {} 
A& a = *pA1;

cout << "pA1 use_count = " << pA1.use_count() << endl;
shared_ptr<A> pA2(pA1);
cout << endl << "After creating pA2" << endl;
cout << "pA1 use_count = " << pA1.use_count() << endl;
cout << "pA2 use_count = " << pA2.use_count() << endl;

pA1.reset();
cout << endl << "After reset" << endl;
cout << "pA1 use_count = " << pA1.use_count() << endl;
cout << "pA2 use_count = " << pA2.use_count() << endl;

This example declares a simple class A with a single member function. You can instantiate a shared_ptr using either a raw pointer to A (as in the case of pA1 in the code above) or through another shared_ptr variable (as in the case of pA2). Operator overloads exist for conversion to Boolean along with the operators -> and &, which allow you to use natural, pointer-like syntax with shared_ptr. When you run the preceding example, the output is:

pA1 use_count = 1

After creating pA2
pA1 use_count = 2
pA2 use_count = 2

After reset
pA1 use_count = 0
pA2 use_count = 1

When the code declares the first shared pointer (pA1) and initializes it to the heap-allocated A object, it has a use_count of one. When it creates pA2, which points to the same underlying heap object, the use_count gets bumped up to two, as shown in the output. Finally, calling the reset method on pA1 releases its association with the underlying heap object, giving it a use_count of zero, while the use_count for pA2 drops to one.

The shared_ptr class can also track a chain of links between instances that point to the same underlying heap object, so if you use pA1 to create a new shared_ptr (pA3), the usage count of all three shared_ptr objects (including pA2, which wasn't involved in the assignment to pA3) will be incremented:

shared_ptr<A> pA1(new A());
shared_ptr<A> pA2(pA1);
cout << endl << "After creating pA2" << endl;
cout << "pA1 use_count = " << pA1.use_count() << endl;
cout << "pA2 use_count = " << pA2.use_count() << endl;

shared_ptr<A> pA3(pA1);
cout << endl << "After creating pA3" << endl;
cout << "pA1 use_count = " << pA1.use_count() << endl;
cout << "pA2 use_count = " << pA2.use_count() << endl;
cout << "pA3 use_count = " << pA3.use_count() << endl;

After creating pA2
pA1 use_count = 2
pA2 use_count = 2

After creating pA3
pA1 use_count = 3
pA2 use_count = 3
pA3 use_count = 3

However, if you retrieve the raw pointer and use that to create a new shared_ptr object, the usage count for the original chain of shared_ptr objects will not get incremented, and you're likely to get an application error caused by resources being freed too early. The following code sample shows two incorrect uses of shared_ptr objects:

cout << endl << "pA2 use_count before pA4 = " << pA2.use_count() << endl;
shared_ptr<A> pA4(pA1.get());
cout << "pA2 use_count after pA4 = " << pA2.use_count() << endl;

size_t pA5 = (size_t)pA3.get();
shared_ptr<A> pA6((A*)pA5);
cout << "pA2 use_count after pA5 = " << pA2.use_count() << endl;

pA2 use_count before pA4 = 3
pA2 use_count after pA4 = 3
pA2 use_count after pA6 = 3
Author's Note: Do not copy the preceding code example—it's intended to show how not to use shared_ptr object.

The classic problem associated with reference counting schemes is circular references, where strong_ptr A points to B, which points to C, which in turn points back to A. As the reference count is not decremented when one of the objects goes out of scope, the resources held by the pointers are not freed. The weak_ptr class, which ships in the same header file as shared_ptr, solves this issue by allowing a pointer to be held without the risk of a circular reference preventing resource cleanup. The weak_ptr class is useful in cases where a referenced object wants to hold a back-pointer to its parent, and the lifetimes of the parent and child are tightly coupled. With tight coupling, the back pointer does not need to hold a strong reference to the parent to keep it alive, and doing so would be dangerous from a circular-reference viewpoint.

This code sample shows the parent-child relationship with the use of weak_ptr. Note that while it passes a shared_ptr into the child's constructor, it is used to create a weak_ptr; the shared_ptr will go out of scope after the constructor completes.

class Child{
private:
 weak_ptr<Parent> pParent;
public:
 Child(shared_ptr<Parent> parent): pParent(parent){}
};

class Parent{
private:
 shared_ptr<Child> pChild;
public:
 Parent()
 {
  pChild = shared_ptr<Child>(new Child(shared_ptr<Parent>(this)));
 }
};

The TR1 additions of shared_ptr and weak_ptr are an important addition to the C++ resource management story. By providing templates that fit with other modern C++ language features, such as const variables and the STL, these classes significantly improve the ability to manage allocation and freeing of resources reliably, including heap memory, database connections, and file handles. The shared_ptr class addresses the problems that have prevented auto_ptr from being a general-purpose solution, so developers should embrace shared_ptr as an essential element in everyday C++ development.

About the Author

Nick Wienholt is an independent Windows and .NET consultant based in Sydney. He is the author of Maximizing .NET Performance and co-author of A Programmers Introduction to C# 2.0 from APress, and specializes in system-level software architecture and development, with a particular focus on performance, security, interoperability, and debugging.

Nick is a keen and active participant in the .NET community. He is the co-founder of the Sydney Deep .NET User group, and writes technical articles for Australian Developer Journal, ZDNet, Pinnacle Publishing, CodeGuru, MSDN Magazine (Australia and New Zealand Edition), and the Microsoft Developer Network. In recognition of his work in the .NET area, Nick was awarded the Microsoft Most Valued Professional Award from 2002 through 2007.






Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Sitemap | Contact Us

Rocket Fuel