A TR1 Tutorial: Smart Pointers

Until TR1, the only smart pointer available in the standard library was auto_ptr; that presents some major disadvantages because of its exclusive ownership model. To address these issues, the new standard offers two new implementations, shared_ptr and weak_ptr, both defined in header <memory>.

Class std::auto_ptr

As mentioned earlier, auto_ptr is based on an exclusive ownership model; each means that two pointers (of this type) cannot point to the same resource. Copying or assigning makes the resource changing its owner, with the source giving the ownership to the destination.

#include <memory>
#include <iostream>

class foo
{
public:
   void print() {std::cout << "foo::print" << std::endl;}
};

int main()
{
   std::auto_ptr<foo> ap1(new foo);
   ap1->print();
   std::cout << "ap1 pointer: " << ap1.get() << std::endl;

   std::auto_ptr<foo> ap2(ap1);
   ap2->print();
   std::cout << "ap1 pointer: " << ap1.get() << std::endl;
   std::cout << "ap2 pointer: " << ap2.get() << std::endl;

   return 0;
}

The output is:

foo::print
ap1 pointer: 0033A790
foo::print
ap1 pointer: 00000000
ap2 pointer: 0033A790

The exact value of the wrapped pointer (0033A790) is not important. The issue here is that, after creating and initializing object ap2, ap1 gave up the ownership of the resource, and its wrapper pointer became NULL.

The major problems introduced by auto_ptr are:

  • Copying and assigning changes the owner of a resource, modifying not only the destination but also the source, which it not intuitive.
  • It cannot be used in STL containers because the constraint that a container’s elements must be copy constructable and assignable does not apply to this class.

What’s New in TR1?

Two new smart pointers were added to the standard template library:

  • shared_ptr: Based on a reference counter model, with the counter incremented each time a new shared pointer object points to the resource, and decremented when the object’s destructor executes; when the counter gets to 0, the resource is released. This pointer is copy constructable and assignable; this makes it usable in STL containers. Moreover, the shared pointer works with polymorphic types and incomplete types. Its major drawback is the impossibility to detect cyclic dependencies, in which case the resources never get released (for example, a tree with nodes having (shared) pointers to children but also to the parent, in which case the parent and the children are referencing each other, in a cycle). To fix this issue, a second smart pointer was created:
  • weak_ptr: Points to a resource referred by a shared pointer, but does not participate in reference counting. When the counters gets to 0, the resource is released, regardless the number of weak pointers referring it; all these pointers are marked as invalid.

The next example shows a similar implementation to the first example, replacing auto_ptr with shared_ptr.

int main()
{
   std::tr1::shared_ptr<foo> sp1(new foo);
   sp1->print();
   std::cout << "sp1 pointer: " << sp1.get() << std::endl;

   std::tr1::shared_ptr<foo> sp2(sp1);
   sp2->print();
   std::cout << "sp1 pointer: " << sp1.get() << std::endl;
   std::cout << "sp2 pointer: " << sp2.get() << std::endl;

   std::cout << "counter sp1: " << sp1.use_count() << std::endl;
   std::cout << "counter sp2: " << sp2.use_count() << std::endl;

   return 0;
}
foo::print
sp1 pointer: 0033A790
foo::print
sp1 pointer: 0033A790
sp2 pointer: 0033A790
counter sp1: 2
counter sp2: 2

As you can see, when sp2 is created, sp1 does not give up the ownership, changing its wrapped pointer to NULL; it only increments the reference counter. When the two shared pointer objects get out of scope, the last one that is destroyed will release the resource.

More by Author

Must Read