Boost shared pointers

From Inkscape Wiki
Jump to navigation Jump to search

Notice: C++11

C++11 offers unique_ptr and shared_ptr, and should be used instead.

Boost shared pointers

The Boost library is a set of peer-reviewed C++ libraries that play well with STL, and some of them are considered for inclusion in future versions of the C++ standard. They are generally regarded as well designed, useful, and powerful. They are also cross platform. All header-only Boost libraries can be used in Inkscape, since Boost is an accepted compile time dependency.

This short tutorial will deal with the use of Boost shared pointers. They can be used to significantly reduce the amount of code needed for efficient memory management. Shared pointers with semantics identical to Boost ones are part of the C++ Library Technical Report 1, and will be included as part of the standard library in a future revision of C++.

If the ownership of objects is clear, investigate whether the simpler and lower overhead std::auto_ptr or boost::scoped_ptr can meet your needs. If you mainly need to use shared_ptr to put smart pointers in containers (since std::auto_ptr cannot be used in this way), investigate Boost pointer containers like boost::ptr_list.

Rationale

The memory management problem is how to guarantee that a program does not have any memory leaks. It can be solved with simple reference conuting if there are no cycles between objects. There are two approaches to solve it when :

  1. Garbage collection. Objects are created and left alone when no longer needed. When the memory is low, a background process kicks in and reclaims memory used by objects that can no longer be reached from the stack. This is the method used in most scripting languages, as well as Java and CLR. However, it has some drawbacks. Firstly, destructors can run at some indeterminate time after the object becomes unused, or never. This precludes using the RAII idiom. Secondly, there is some memory overhead associated with maintaining a garbage-collected heap. Thirdly, it requires some type information to be preserved at runtime, to determine where the garbage collector should look for object references - otherwise unrelated data can alias pointers, preventing some objects from being colleted.
  2. Shared and weak pointers. This approach uses two types of object references to break the cycles. A shared pointer will keep the pointed object in memory, while a weak pointer can be used to obtain a shared pointer to the same object, but will not prevent the object from destruction. This approach requires more programming effort and adds some (negligible) overhead to each operation on the shared pointer, but makes object destruction predictable.

The second method suits C++ better than garbage collection. Firstly, there is generally little to no type information present at runtime to determine where to look for pointers to objects; Garbage collectors for C++ and C must assume that all data on the stack and all contents of the objects' memory are pointers to other objects (also known as conservative garbage collection). This can cause some unused memory to never be freed, because some integer in some object happens to have the same value as some other object's address. Secondly, since the C++ standard library and other libraries frequently use the RAII idiom, many C++ objects (like std::fstream) have non-trivial destructors. If those destructors are not run right after the object becomes unused, the program may hold on to resources it no longer uses, like file descriptors or database connections. Finally, using tricks like storing the color of a red-black tree node in the low bit of a pointer can in theory cause objects to be collected before they become unused, bu this can be mitigated with anchoring.

Shared pointers

Shared pointer is a smart pointer (a C++ object wih overloaded operator*() and operator->()) that keeps a pointer to an object and a pointer to a shared reference count. Every time a copy of the smart pointer is made using the copy constructor, the reference count is incremented. When a shared pointer is destroyed, the reference count for its object is decremented. Shared pointers constructed from raw pointers initially have a reference count of 1. When the reference count reaches 0, the pointed object is destroyed, and the memory it occupies is freed. You do not need to explicitly destroy objects: it will be done automatically when the last pointer's destructor runs.

Usage

When creating a new object with new, use it as a constructor argument of a boost::shared_ptr.

boost::shared_ptr<Foo> foo_ptr(new Foo());

You can reassign the pointer to a new object using the member function reset(). This will decrease the reference count of the old object, if any, and reinitialize the object with the argument and a reference count of 1.

foo_ptr.reset(new Foo());

Note however that you cannot obtain a shared pointer to an object from a raw pointer. This will cause a segmentation fault:

void some_function(Foo *f) {
   boost::shared_ptr<Foo> fptr(f);
   fptr->something();
}
...
Foo *foo = new Foo();
some_function(foo);
foo->something();

That's because the temporary fptr is initialized with a reference count of 1. After the function returns, the reference count is zero and the object is destroyed. foo->something() will then fail, because foo no longer points to a valid object.

An uninitialized shared pointer is empty. This is equivalent to NULL for raw pointers. You can check for an empty shared pointer using this code:

if (foo) {
   // foo points to an object
} else {
   // foo is empty
}

A raw pointer can be retrieved using the get() method.

Foo *rawptr = fptr.get();

Weak pointers

Weak pointers can only be used to obtain a shared pointer to the same object, and check whether the object was already destroyed. They can be used to break circular references between objects. A weak pointer that points to an object that was already destroyed is called expired. Empty weak pointers (that don't point to any object) are also expired.

Here is an example:

boost::shared_ptr<Foo> foo(new Foo());
boost::weak_ptr<Foo> weakfoo(foo);
...
boost::shared_ptr<Foo> fptr = weakfoo.lock();
if (fptr) {
   // object still exists
} else {
   // object already destroyed
}

Weak pointers cannot be dereferenced for thread safety reasons. If some other thread destroyed the object after you checked the weak pointer for expiry but before you used it, you would get a crash. Shared pointers can be obtained from weak pointers using use of two methods:

  1. boost::shared_ptr<Foo> fptr = weakfoo.lock(); - fptr will be empty if weakfoo is expired. An exception will never be thrown.
  2. boost::shared_ptr<Foo> fptr(weakfoo); - the exception std::tr1::bad_weak_ptr will be thrown if weakfoo is expired.

Shared pointers in member functions

Sometimes a shared pointer to the current object is needed in its member function. Boost provides a mixin template class called enable_shared_from_this, which defines a no-argument member function called shared_from_this(). It returns a shared pointer to this. At least one instance of a shared pointer to this object must exist before the first use of this method; otherwise it has undefined results (usually crash). The best way to guarantee that this is condition met is to make the constructor protected and provide a factory method that returns a shared pointer to the newly created object.

class Foo : public boost::enable_shared_from_this<Foo> {
public:
   void someMethod() {
      boost::shared_ptr<Foo> this_ = shared_from_this();
      // use pointer...
   }
   ...
   static boost::shared_ptr<Foo> create() {
      return boost::shared_ptr<Foo>(new Foo());
   }
protected:
   Foo() { ... }
   ...
};

See also