SatView: Pointer Perfect, part 3.5

In this article in our ongoing series on pointers, J. Nakamura talks about some additional smart pointers, their advantages and drawbacks, and when and where you would employ them.

In the previous article we saw that the usage of pointers is not entirely trivial, if only proven by the numerous garbage collectors developed for today’s modern programming languages. The usage of smart pointers can help make the usage of pointers in C++ a lot safer, although the std::auto_ptr provided by the STL has some counter-intuitive properties.

The Boost library (www.boost.org) provides additional smart pointers, all with their advantages and drawbacks. Still you will find that they will cover most of the situations where you would like to employ a smart pointer. I highly recommend you check Boost out, since it offers lots more than just smart pointers.

In this article I will only try to motivate you to use these smart pointers. It is not my goal to provide a complete description of the interfaces… please go to www.boost.org for the latest documentation instead.

BOOST::SCOPED_PTR.

This is the simplest of all the smart pointers Boost offers. It basically behaves the same as const std::auto_ptr and its name makes its intention quite clear: it frees the allocated memory it owns when it goes out of scope (by leaving a function or by moving on to the next iteration of a loop for example), or by calling its reset() function.

Being simple, the scoped_ptr comes without shared-ownership or transfer-of-ownership semantics. You should understand by now that because of this behavior, we cannot store a scoped_ptr in (STL) containers.

The scoped_ptr is actually so simple that it doesn’t need any member variables and has therefore no more space overhead than that of a built-in pointer. The code example of the previous article can easily be adopted to the usage of a boost::scoped_ptr:

void foo() {
boost::scoped_ptr<MyClass> pMyObj(new MyClass);
/* perform some operations here */
}

The main reason why you should prefer using this pointer over the const std::auto_ptr is that its name makes it clear when the pointer is being deleted. While the auto_ptr allows you to transfer ownership, the scoped_ptr strictly forbids it. It is also possible to free the object pointed at and store a new pointer in a scoped_ptr, while the equivalent usage of a const auto_ptr will prohibit you from doing this. The scoped pointer is pretty useful to use as a member of a class to ensure that the resource it is holding is freed when the object is destructed.

scoped_ptr cannot correctly deal with a pointer to a dynamically allocated array. In one of the previous articles you saw that the new[] operator has to be matched with the delete[] operator, or otherwise the result would be undefined and most likely result in only the first object in the array being deleted. The scoped_ptr uses the delete operator in its destructor to free the object; thus it cannot correctly destruct dynamically allocated arrays.

Use the boost::scoped_array to manage pointers to dynamically allocated arrays. Its behavior is like that of a scoped_ptr with the difference that the delete[] operator is used in its destructor and it doesn’t implement the operator* and operator-> , but the operator[] instead.

{mospagebreak title=BOOST::SHARED_PTR.}

Your concept of ownership might lure you into the intuitive view of regarding a smart pointer to be reference counted. There are different ways to interpret the behavior “the smart pointer deletes the object it holds when it is no longer needed.”

In fact, you have seen that ownership is transferred when auto_ptrs are copied, rendering the original auto_ptr invalid (i.e. you should not dereference it anymore), while you would expect a smart pointer to remain valid after a copy of it was made. Objects are normally not modified when they are being copied! You might like the object held by the smart pointer to be deleted when both the original smart pointer and its copy are not needed anymore. In that case you need a reference counted smart pointer like the boost::shared_ptr.

The boost::shared_ptr tracks how many pointers are referring to an object and frees it only after the last pointer is not needed anymore. Let’s adopt the code example again:

void foo() {
boost::shared_ptr<MyClass> pMyObj(new MyClass);
/* perform some operations here */
boost::shared_ptr<MyClass> pMyObj2 = pMyObj;
(void)printf(“MyClass: %d references.n”, pMyObj->use_count());
}

The use_count() function is useful when debugging a boost::shared_ptr, because it returns the reference count of the smart pointer (2 in this example). MyClass is still destructed when we leave the scope of this function, so this example doesn’t really highlight the differences between the scoped_ptr and the auto_ptr. Lets look at another code example.

First we need some includes:
#include <stdio.h>
#include <memory>
#include <boost/scoped_ptr.hpp>
#include <boost/shared_ptr.hpp>

Then we redefine MyClass:

class MyClass {
public:
  MyClass()  { (void)printf(“>> MyClass constructed <<n”); }
  ~MyClass()  { (void)printf(“>> MyClass destructed <<n”); }
};


And define three functions, that accept a smart pointer as a function parameter:

void foo(std::auto_ptr<MyClass>) {
  (void)printf(ttenter foo(auto_ptr).n”);
  (void)printf(ttdo some operations here.n”);
  (void)printf(ttleave foo(auto_ptr).n”);
}
void foo(boost::scoped_ptr<MyClass>) {
(void)printf(ttenter foo(scoped_ptr).n”);
(void)printf(ttdo some operations here.n”);
(void)printf(ttleave foo(scoped_ptr).n”);
}
void foo(boost::shared_ptr<MyClass>) {
  (void)printf(ttenter foo(shared_ptr).n”);
  (void)printf(ttdo some operations here.n”);
(void)printf(ttleave foo(shared_ptr).n”);
}

Finally a test function and main:

void test() {
  (void)printf(tenter test().n”);

std::auto_ptr<MyClass> ptrAuto(new MyClass);
  (void)printf(“ttest() – calling foo(auto_ptr).n”);
  foo(ptrAuto);
  (void)printf(“ttest() – foo(auto_ptr) finished.nn”);

  boost::scoped_ptr<MyClass> ptrScoped(new MyClass);
  foo(ptrScoped);

  boost::shared_ptr<MyClass> ptrShared(new MyClass);
  (void)printf(“ttest() – calling foo(shared_ptr).n”);
  foo(ptrShared);
  (void)printf(“ttest() – calling foo(shared_ptr finished.nn”);

(void)printf(tleave test().n”);
}
int main(int argc, char *argv[])
{
  (void)printf(“main – calling test().n”);
  test();
  (void)printf(“main – test() finished.n”);
  return 0;
}

The first thing you will notice when trying to compile this (or did you notice while reading the code?) is that void foo(boost::scoped_ptr<MyClass>) is dead in the water. The compiler will refuse to compile it and complain about not being able to access the privately declared copy constructor in scoped_ptr. This is the way this smart pointer enforces its rule that it cannot transfer ownership! Passing objects by value always forces the object passed to to be copied  from the object passed in in order to be created.

So after you remove the declaration of the scoped_ptr and the call to foo(scoped_ptr) you will be able to compile it. Run it and you will get the following output:

main – calling test().
        enter test().
>> MyClass constructed <<
        test() – calling foo(auto_ptr).
                enter foo(auto_ptr).
                do some operations here.
                leave foo(auto_ptr).
>> MyClass destructed <<
        test() – foo(auto_ptr) finished.

>> MyClass constructed <<
        test() – calling foo(shared_ptr).
                enter foo(shared_ptr).
                do some operations here.
                leave foo(shared_ptr).
        test() – foo(shared_ptr) finished.

        leave test().
>> MyClass destructed <<

main – test() finished.

Look carefully at the moments MyClass is constructed and destructed. Both times MyClass is newed in our test() function. When the pointer to MyClass is contained in an auto_ptr, it is deleted the moment the last auto_ptr holding ownership of it isn’t needed anymore: the moment we leave the scope of foo(auto_ptr).

The moment of destruction is different when the pointer is contained in a shared_ptr. MyClass only gets deleted when there is no more shared_ptr referencing it: the moment we leave the scope of test()! The reason for this is that MyClass is freed when the internal reference count of the shared_ptr reaches zero.

Reference counting is done in the constructor and destructor of the shared pointer. Upon construction the reference counter is set to one. When it is passed by value (i.e. another shared_ptr gets copy-constructed) to foo(shared_ptr), the reference counter goes up to two. As soon as we leave that function, the destructor decreases the reference counter by one. And when we leave the test() function, the destructor of the shared_ptr  inside the scope of that function will decrease the reference count by one to zero. MyClass is not referenced anymore and the destructor will finally delete it. Due to this behavior, the boost::shared_ptr can be properly used with (STL) containers.

Shared pointers can be pretty useful as members of a class, for example when you are making use of the pimpl idiom.

Like the boost::scoped_ptr, the boost::shared_ptr cannot correctly hold a pointer to a dynamically allocated array. Use the boost::shared_array for this purpose.

A special note has to be made on passing smart pointers on to functions:

void f(boost::shared_ptr<int>, int);
int g();
void ok() {
boost::shared_ptr<int> p(new int(2));
f(p,g());
}
void bad() {
  f(shared_ptr<int>(new int(2)), g());
}

The bad() functions constructs a temporary shared_ptr in place, creating the possibility of a memory leak. Function arguments are evaluated in unspecified order, making it possible to evaluate new int(2) first, followed by g() which might throw an exception causing the shared_ptr never to be constructed and new int(2) never to be freed.

See also Herb Sutter’s treatment on this topic.

{mospagebreak title=SMART PIMPL}

Pimpls are effective when you want to hide the private implementation of a class from its users and only publish its public interface (see my article about Pimpls). The characteristics of pointers lay at the basis of a pimpled implementation and we have seen that when we use built-in pointers, there are many ways to (accidentally) create memory-leaks. This looks like a great opportunity to use smart pointers, but we will see that our choice must be narrowed down to the usage of the boost::shared_ptr if we are to implement a class with its private implementation completely hidden in the source file. The reason for this is that the boost::shared_ptr is the only smart pointer allowing for incomplete types to be assigned to it.

But what better way to find out than by code examples? Let’s try to implement the pimpl using all three types of smart pointers. The following classes don’t do anything, except demonstrate the usage of smart pointers when implementing pimpls.

// file: pimpl_auto.h
#ifndef __PIMPL_AUTO_H
#define __PIMPL_AUTO_H

class AutoImpl; // forward declaration
class PimplAuto {
private:
  std::auto_ptr<AutoImpl> m_pImpl;
};
#endif
// eof

// file: pimpl_scoped.h
#ifndef __PIMPL_SCOPED_H
#define __PIMPL_SCOPED_H

class ScopedImpl; // forward declaration
class PimplScoped {
private:
  boost::scoped_ptr<ScopedImpl> m_pImpl;
};
#endif
// eof

// file: pimpl_shared.h
#ifndef __PIMPL_SHARED_H
#define __PIMPL_SHARED_H

class SharedImpl; // forward declaration.
class PimplShared {
private:
  boost::shared_ptr<SharedImpl> m_pImpl;
};

#endif
// eof

Don’t be fooled in thinking that all these classes work after you included them, because the compiler will only start complaining when you try to instantiate them.

#include <memory>
#include <boost/scoped_ptr.hpp>
#include <boost/shared_ptr.hpp>

#include “pimpl_auto.h”
#include “pimpl_scoped.h”
#include “pimpl_shared.h”

int main(int argc, char *argv[])
{
  PimplAuto test1;
  PimplScoped test2;
  PimplShared test3;
  return 0;
}

In the case of test1 (using the std::auto_ptr), the compiler cannot work out what the destructor for the private implementation looks like, which is fair since we have only forward declared it. The solution would be to separate the interface of the private implementation into a separate header file, so that we can include it in the header file of the public interface. This is quite common when implementing the Bridge pattern, but I prefer not to expose the private interface in any way when implementing a pimpl.

The same happens with test2 (using the boost::scoped_ptr). The compiler needs to determine how to destroy the private implementation, but fails since the type is incomplete. The only smart pointer that can deal with the incomplete type at this point turns out to be the boost::shared_ptr, which compiles (and runs) fine.

{mospagebreak title=BOOST::WEAK_PTR}

The boost::weak_ptr stores a ‘weak’ reference to an object that is already being managed by a boost::shared_ptr. It has no effect on the reference count of the shared pointer. Boost provides you with a weak_ptr, because it is very easy to create a circular reference with shared_ptrs. Check out the following relationship (which by the way can only be created with shared_ptrs since these allow for incomplete types):

#include <boost/shared_ptr.hpp>

class Parent;  // forward declaration
class Child;  // forward declaration

class Parent {
public:
boost::shared_ptr<Child> m_pChild;
};

class Child {
public:
  boost::shared_ptr<Parent> m_pParent;
};

int main(int argc, char *argv[]) {
  boost::shared_ptr<Parent> parent(new Parent);
  boost::shared_ptr<Child> child(new Child);
  parent->m_pChild = child;
  child->m_pParent = parent;

  child.reset();  // letting go of pointer
  (void)printf(“child’s ptr is 0x%p.n”, child.get());
  (void)printf(“child’s refcount is %d.n”,
    parent->m_pChild.use_count());
  return 0;
}

When you compile and run this example, your output will be:

child’s ptr is 0×00000000.

child’s refcount is 1.

We introduced a circular reference by letting the parent point to the child, which in turn points back to the parent. When the child is created its reference counter is set to 1 and as soon as it is copied into the parent, its reference counter is increased to 2. Same story goes for the parent.

Now if we are to reset/destroy the child smart pointer, parent will still hold a reference to it. If we are to reset/destruct the parent smart pointer, that one will still have a reference count of 1 as well: neither will have destroyed the object they contain. But child cannot be used anymore, since it contains a NULL pointer! The same is possible with parent and we will have lost access to both objects, which have not been destructed because their reference count is still 1!

To prevent cycles like these, you can use the boost::weak_ptr. Child could break the circle like this:

class Child {
public:
  boost::weak_ptr<Parent> m_pParent;
};

shared_ptrs manage an object by observing how many pointers are making use of it, hence keeping it alive (which becomes a problem with circular references). A weak_ptr only references that object but is neglected in the efforts used to keep it alive. Thus when the last shared_ptr is destructed and the object is deleted, the weak_ptr becomes invalid. If you need access to the object contained in a weak_ptr, you can convert it to a shared_ptr using a constructor designed for this purpose (unless the object has been deleted by the last shared_ptr referencing it of course).

#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>

int main(int argc, char *argv[]) {
boost::shared_ptr<int> pOne(new int(1));
boost::shared_ptr<int> pOther = pOne;
boost::weak_ptr<int> pWeak(pOne);
(void)printf(“ref count = %d.n”, pOne.use_count());
pOther.reset();
(void)printf(“ref count = %d.n”, pOne.use_count());
boost::shared_ptr<int> pFromWeak(pWeak);
(void)printf(“ref count = %d.n”, pFromWeak.use_count());
pOne.reset();
(void)printf(“ref count = %d.n”, pFromWeak.use_count());
pFromWeak.reset();
try { boost::shared_ptr<int> pRestore(pWeak); }
catch (…) { (void)printf(“exception thrown!n”); }

return 0;
}

The code above provides the following output:

ref count = 2.
ref count = 1.
ref count = 2.
ref count = 1.

exception thrown!

So weak_ptr is useful to break circular references, though you have to make sure you never try to restore something that is not there anymore.

Next article will be the last on pointers and discuss/demonstrate some of the common pitfalls you will encounter when using them. I hope you have enjoyed the trip so far. If smart pointers have captured your interest, be sure to check out www.boost.org and read up on the boost::intrusive_ptr when size matters to you (sometimes size matters, sometimes how you use it right?).

Join the conversation
about this article

View thread