shared_ptr


Header: "boost/shared_ptr.hpp"

Almost all non-trivial programs need some form of reference-counted smart pointers. These smart pointers eliminate the need to write complicated logic to control the lifetime of objects shared among two or more other objects. When the reference count drops to zero, no more objects are interested in the shared object, and so it is deleted automatically. Reference-counted smart pointers can be categorized as intrusive or non-intrusive. The former expects the classes that it manages to provide certain functionality or data members with which to manage the reference count. That means designing classes with the foresight to work with an intrusive, reference-counted smart pointer class, or retrofitting. Non-intrusive, reference-counted smart pointers don't require anything of the types they manage. Reference-counted smart pointers assume ownership of the memory associated with their stored pointers. The problem with sharing objects without the help of smart pointers is that someone must, eventually, delete the shared memory. Who, and when? Without reference-counted smart pointers, one must impose lifetime management externally to the memory being managed, which typically means stronger dependencies among the collective owners. That, in turn, impedes reusability and adds complexity.

The class to be managed may have properties that make it a good candidate for use with a reference-counted smart pointer. For example, the fact that it is expensive to copy, or that part of its representation needs to be shared between instances, make shared ownership desirable. There are also situations in which there is no explicit owner of a shared resource. Using reference-counted smart pointers makes possible sharing ownership among the objects that need access to the shared resource. Reference-counted smart pointers also make it possible to store pointers to objects in Standard Library containers without risk of leaks, especially in the face of exceptions or when removing elements from the containers. When you store pointers in containers, you can take advantage of polymorphism, improved efficiency (if copying is expensive), and the ability to store the same objects in multiple, associated containers for specialized lookups.

After you've determined that the use of a reference-counted smart pointer is warranted, how do you choose whether to use an intrusive or non-intrusive design? Non-intrusive smart pointers are almost always the better choice on account of their general applicability, lack of impact on existing code, and flexibility. You can use non-intrusive, reference-counted smart pointers with classes that you cannot or don't wish to change. The usual way to adapt a class to work with an intrusive, reference-counted smart pointer is to derive from a reference-counted base class. That change may be more expensive than appears at first glance. At the very least, it adds dependencies and decreases reusability.[6] It also typically increases object size, which may limit usability in some contexts.[7]

[6] Consider the need to use more than one reference-counted smart pointer class with the same type. If both are intrusive designs, the different base classes may not be compatible and will certainly be wasteful. If only one is an intrusive design, the overhead of the base class is for naught when using the non-intrusive smart pointer.

[7] On the other hand, non-intrusive smart pointers require additional storage for the actual smart pointer.

A shared_ptr can be constructed from a raw pointer, another shared_ptr, a std::auto_ptr, or a boost::weak_ptr. It is also possible to pass a second argument to the constructor of shared_ptr, known as a deleter. The deleter is later called upon to handle deletion of the shared resource. This is useful for resource management where the resource is not allocated with new and destroyed with delete (we shall see examples of creating custom deleters later). After the shared_ptr has been constructed, it is used just like an ordinary pointer, with the obvious exception that it must not be explicitly deleted.

This is a partial synopsis for shared_ptr; the most important members and accompanying free functions are shown and subsequently briefly discussed.

 namespace boost {   template<typename T> class shared_ptr {   public:     template <class Y> explicit shared_ptr(Y* p);     template <class Y,class D> shared_ptr(Y* p,D d);     ~shared_ptr();     shared_ptr(const shared_ptr & r);     template <class Y> explicit        shared_ptr(const weak_ptr<Y>& r);     template <class Y> explicit shared_ptr(std::auto_ptr<Y>& r);     shared_ptr& operator=(const shared_ptr& r);     void reset();         T& operator*() const;     T* operator->() const;     T* get() const;     bool unique() const;     long use_count() const;     operator unspecified-bool-type() const;     void swap(shared_ptr<T>& b);   };   template <class T,class U>     shared_ptr<T> static_pointer_cast(const shared_ptr<U>& r); } 

Members

 template <class Y> explicit shared_ptr(Y* p); 

This constructor takes ownership of the supplied pointer p. The argument p must be a valid pointer to Y. The reference count is set to 1 after construction. The only exception that may be thrown from the constructor is std::bad_alloc (which can only happen in the unlikely event that the reference counter cannot be allocated from the free store).

 template <class Y,class D> shared_ptr(Y* p,D d); 

This constructor takes two arguments. The first is the resource that the shared_ptr should take ownership of, and the second is an object that is responsible for releasing that resource when the shared_ptr is destroyed. The stored resource is passed to the object as d(p). Thus, valid values of p depend upon d. If the reference counter cannot be allocated, shared_ptr tHRows an exception of type std::bad_alloc.

 shared_ptr(const shared_ptr& r); 

The stored resource in r is shared by the constructed shared_ptr, and the reference count is increased by one. This copy constructor never throws.

 template <class Y> explicit shared_ptr(const weak_ptr<Y>& r); 

Constructs a shared_ptr from a weak_ptr (covered later in this chapter). This enables thread-safe usage of weak_ptr, because the reference count of the shared resource pointed to by the weak_ptr argument will be incremented (weak_ptrs do not affect the reference count of shared resources). If the weak_ptr is empty (r.use_count()==0), shared_ptr tHRows an exception of type bad_weak_ptr.

 template <typename Y> shared_ptr(std::auto_ptr<Y>& r); 

The construction from an auto_ptr takes ownership of the pointer stored in r by storing a copy of the pointer and calling release on the auto_ptr. The reference count after construction is 1. r is, of course, emptied. Throws std::bad_alloc if the reference counter cannot be allocated.

 ~shared_ptr(); 

The shared_ptr destructor decreases the reference count by one. If the count is then zero, the stored pointer is deleted. Deleting the pointer is done through a call to operator delete or, if a custom deleter object was supplied to handle destruction, that object will be called with the stored pointer as its sole argument. The destructor never throws.

 shared_ptr& operator=(const shared_ptr& r);   

The copy assignment operator shares the resource in r and stops sharing the resource currently being shared. The copy assignment operator never throws.

 void reset(); 

The reset function is used to stop sharing ownership of the stored pointer. The reference count for the shared resource is decremented.

 T& operator*() const; 

This operator returns a reference to the object pointed to by the stored pointer. If the pointer is null, invoking operator* results in undefined behavior. This operator never throws.

 T* operator->() const; 

The operator returns the stored pointer. This, together with operator* is what makes the smart pointer look like an ordinary pointer. This operator never throws.

 T* get() const; 

The get function is the preferred way of retrieving the stored pointer when it might be null (in which case operator* and operator-> leads to undefined behavior). Note that it is also possible to test whether a shared_ptr contains a valid pointer by using the implicit Boolean conversion. This function never throws.

 bool unique() const; 

This function returns true if the shared_ptr is the sole owner of the stored pointer; otherwise, it returns false. unique never throws.

 long use_count() const; 

The use_count function returns the reference count for the pointer. It is especially useful for debugging purposes, because it can be used to get snapshots of the reference count at critical points of program execution. Use it sparingly. For some possible implementations of the shared_ptr interface, calculating the reference count may be expensive or even impossible. The function never throws.

 operator unspecified-bool-type() const; 

This implicit conversion to a type, unspecified-bool-type, makes it possible to test a smart pointer in Boolean contexts. The value is TRue if the shared_ptr is currently storing a valid pointer; otherwise, it is false. Note that the type that this conversion function returns is not specified. Using bool as the return type allows for some nonsensical operations, so typically, an implementation uses the safe bool idiom,[8] which is a nifty way of ensuring that only applicable Boolean tests can be used. The function never throws.

[8] Invented by Peter Dimov.

 void swap(shared_ptr<T>& b); 

It is sometimes convenient to swap the contents of two shared_ptrs. The swap function exchanges the stored pointers (and their reference counts). This function never throws.

Free Functions

 template <typename T,typename U>   shared_ptr<T> static_pointer_cast(const shared_ptr<U>& r);  

To perform a static_cast on a pointer stored in a shared_ptr, we could retrieve the pointer and then cast it, but we couldn't store it in another shared_ptr; the new shared_ptr would think it was the first to manage the resource the pointer refers to. This is remedied by static_pointer_cast. Using this function ensures that the reference count for the pointee remains correct. static_pointer_cast never throws exceptions.

Usage

The primary problem solved by shared_ptr is knowing the correct time to delete a resource that is shared by more than one client. Here's a straightforward example, where two classes, A and B, are sharing an instance of int. To start using boost::shared_ptr, you need to include "boost/shared_ptr.hpp".

 #include "boost/shared_ptr.hpp" #include <cassert> class A {   boost::shared_ptr<int> no_; public:   A(boost::shared_ptr<int> no) : no_(no) {}   void value(int i) {     *no_=i;   } }; class B {   boost::shared_ptr<int> no_; public:   B(boost::shared_ptr<int> no) : no_(no) {}   int value() const {     return *no_;   } }; int main() {     boost::shared_ptr<int> temp(new int(14));     A a(temp);     B b(temp);     a.value(28);     assert(b.value()==28); } 

The classes A and B both store a shared_ptr<int>. When creating the instances of A and B, the shared_ptr temp is passed to their constructors. This means that all three shared_ptrsa, b, and tempare now referring to the same instance of an int. Had we used pointers to achieve such sharing of an int, A and B would have had a hard time figuring out when (and if!) it should be deleted. In the example, the reference count is 3 until the end of main, where all of the shared_ptrs go out of scope, decreasing the count until it reaches 0, allowing the last of the smart pointers to delete the shared int.

The Pimpl Idiom Revisited

The pimpl idiom was previously presented in conjunction with scoped_ptr, which works well as a means of storing the dynamically allocated instance of the pimpl, if copying is not permitted for the class using the idiom. That is not appropriate for all classes that would benefit from using the pimpl idiom (note that scoped_ptr can still be used, but copy construction and assignment need to be implemented by hand). For those classes that can handle shared implementation details, shared_ptr comes into play. When ownership of the pimpl is passed to a shared_ptr, the copying and assignment operators come for free. You'll recall that when using scoped_ptr to handle the lifetime of the pimpl class, copying of the outer class is not allowed, because scoped_ptrs are not copyable. This means that to support copying and assignment in such classes, a copy constructor and assignment operator must be defined manually. When using shared_ptr to handle the lifetime of the pimpl, a user-defined copy constructor may not even be needed. Note that the pimpl instance will be shared among the objects of the class, so if there is state that only applies to one instance of the class, a handcrafted copy constructor is still required. The solution is very similar to what we saw for scoped_ptr; just make it a shared_ptr, instead.

shared_ptr and Standard Library Containers

Storing objects directly in a container is sometimes troublesome. Storing objects by value means clients get copies of the container elements, which may be a performance problem for types where copying is an expensive operation. Furthermore, some containers, notably std::vector, copy elements when resizing as you add more elements, further adding to the performance problems. Finally, value semantics means no polymorphic behavior. If you need to store polymorphic objects in a container and you don't want to slice them, you must use pointers. If you use raw pointers, the complexity of maintaining the integrity of the elements skyrockets. That is, you must know whether clients of the container still refer to elements of the container when erasing them from the container, never mind coordinating multiple clients using the same element. Such problems are solved handily by shared_ptr.

The following example shows how to store shared pointers in a Standard Library container.

 #include "boost/shared_ptr.hpp" #include <vector> #include <iostream> class A { public:   virtual void sing()=0; protected:   virtual ~A() {}; }; class B : public A { public:   virtual void sing() {     std::cout << "Do re mi fa so la";   } }; boost::shared_ptr<A> createA() {   boost::shared_ptr<A> p(new B());   return p; } int main() {   typedef std::vector<boost::shared_ptr<A> > container_type;   typedef container_type::iterator iterator;   container_type container;   for (int i=0;i<10;++i) {     container.push_back(createA());   }   std::cout << "The choir is gathered: \n";   iterator end=container.end();   for (iterator it=container.begin();it!=end;++it) {     (*it)->sing();   } } 

The two classes, A and B, contain a single virtual member function sing. B derives publicly from A, and as you can see, the factory function createA returns a dynamically allocated instance of B wrapped in a shared_ptr<A>. In main, a std::vector containing shared_ptr<A> is filled with 10 elements, and finally sing is invoked on each element. Had we been using raw pointers as elements, the objects would need to be manually deleted. In the example, this deletion is automatic, because the reference count of each shared_ptr in the container is 1 as long as the vector is kept alive; when the vector is destroyed, the reference counters all go down to zero, and the objects are deleted. It is interesting to note that even if the destructor of A had not been declared virtual, shared_ptr would have correctly invoked the destructor of B!

A powerful technique is demonstrated in the example, and it involves the protected destructor in A. Because the function createA returns a shared_ptr<A>, it won't be possible to invoke delete on the pointer returned by shared_ptr:: get. This means that if the pointer in the shared_ptr is retrievedperhaps in order to pass it to a function expecting a raw pointerit won't be possible to accidentally delete it, which would wreak havoc. So, how is it that the shared_ptr is allowed to delete the object? It's because of the actual type of the pointer, which is B; B's destructor is not protected. This is a very useful way of adding extra safety to objects kept in shared_ptrs.

shared_ptr and Other Resources

Sometimes, you'll find yourself in need for using shared_ptr with a type that requires other cleanup than a simple delete. There is support for such cases in shared_ptr tHRough what is called custom deleters. Resource handles, such as FILE*, or operating systemspecific handles, are typically released through an operation such as fclose. To use a FILE* in a shared_ptr, we define a class that is responsible for deallocating the resource.

 class FileCloser { public:    void operator()(FILE* file) {     std::cout << "The FileCloser has been called with a FILE*, "       "which will now be closed.\n";     if (file!=0)        fclose(file);   } }; 

This is the function object that we'll use to make sure that fclose is called when the resource should be released. Here's an example program that utilizes our FileCloser class.

 int main() {   std::cout <<      "shared_ptr example with a custom deallocator.\n";    {     FILE* f=fopen("test.txt","r");     if (f==0) {       std::cout << "Unable to open file\n";       throw "Unable to open file";     }     boost::shared_ptr<FILE>        my_shared_file(f, FileCloser());     // Position the file pointer     fseek(my_shared_file.get(),42,SEEK_SET);   }   std::cout << "By now, the FILE has been closed!\n"; } 

Note that to get the resource, we need to use the unpronounceable &* idiom, get, or get_pointer on the shared_ptr. (I clearly caution against using &*. The choice between the other two is less clear.) The example could be made even simplerif we don't need to do more than call a single argument function when deallocating, there's really no need to create a custom deleter class at all. The example could be rewritten as follows:

 {   FILE* f=fopen("test.txt","r");   if (f==0) {     std::cout << "Unable to open file\n";     throw file_exception();   }      boost::shared_ptr<FILE> my_shared_file(f,&fclose);   // Position the file pointer   fseek(&*my_shared_file,42,SEEK_SET);  } std::cout << "By now, the FILE* has been closed!\n"; 

Custom deleters are extremely useful for handling resources that need a special release procedure. Because the deleter is not part of the shared_ptr type, clients need not know anything about the resource that the smart pointer owns (besides how to use it, of course!). For example, a pool of objects can be used, and the custom deleter would simply return the object to the pool. Or, a singleton object could have a deleter that does nothing.

Security Through Custom Deleters

We've already seen how using a protected destructor in a base class helps add safety to classes used with shared_ptr. Another way of achieving the same level of safety is to declare the destructor protected (or private) and use a custom deleter to take care of destroying the object. This custom deleter must be made a friend of the class that it is to delete for this to work. A nice way to encapsulate this deleter is to implement it as a private nested class, like the following example demonstrates:

 #include "boost/shared_ptr.hpp" #include <iostream> class A {   class deleter {     public:       void operator()(A* p) {         delete p;       }   };   friend class deleter; public:   virtual void sing() {     std::cout << "Lalalalalalalalalalala";   }   static boost::shared_ptr<A> createA() {     boost::shared_ptr<A> p(new A(),A::deleter());     return p;   } protected:   virtual ~A() {}; }; int main() {   boost::shared_ptr<A> p=A::createA(); } 

Note that we cannot use a free function as a factory for shared_ptr<A> here, because the nested deleter class is private to A. Using this scheme, it isn't possible for users to create As on the stack, and it isn't possible to call delete using a pointer to A.

Creating a shared_ptr from this

Sometimes, it is necessary to obtain a shared_ptr from thisthat is, you are making the assumption that your class is being managed by a shared_ptr, and you need a way to convert "yourself" into that shared_ptr. Sounds like a mission impossible? Well, the solution comes from a smart pointer component that we've yet to discussboost::weak_ptr. A weak_ptr is an observer of shared_ptrs; it just silently sits and watches them, but does not affect the reference count. By storing a weak_ptr to this as a member of the class, it's possible to retrieve a shared_ptr to this on demand. To relieve you from the tedium of having to write the code for storing a weak_ptr to this and subsequently obtain a shared_ptr from that weak_ptr, Boost.Smart_ptr provides a helper class for this task, called enable_shared_from_this. Simply have your class derive publicly from enable_shared_from_this, and then use the function shared_from_this whenever you need to access the shared_ptr that is managing this. Here's an example that demonstrates how enable_shared_from_this is used:

 #include "boost/shared_ptr.hpp" #include "boost/enable_shared_from_this.hpp" class A; void do_stuff(boost::shared_ptr<A> p) {   ... } class A : public boost::enable_shared_from_this<A> { public:   void call_do_stuff() {     do_stuff(shared_from_this());   } }; int main() {   boost::shared_ptr<A> p(new A());   p->call_do_stuff(); } 

The example also demonstrates a case where you need the shared_ptr that is managing this. Class A has a member function call_do_stuff that needs to call the free function do_stuff, which expects an argument of type boost:: shared_ptr<A>. Now, in A::call_do_stuff, this is simply a pointer to A, but because A derives from enable_shared_from_this, calling shared_from_this returns the shared_ptr that we're seeking. In shared_from_this, which is a member of enable_shared_from_this, the internally stored weak_ptr is converted to a shared_ptr, thereby increasing the reference count to make sure that the object is not deleted.

Summary

Reference-counted smart pointers are extremely important tools. Boost's shared_ptr provides a solid and flexible solution that is proven through extensive use in many environments and circumstances. It is common to need to share objects among clients, and that often means that there is no way of telling if, and when, the object can be deleted safely. shared_ptr insulates clients from knowing about what other objects are using a shared object, and relieves them of the task of releasing the resource when no objects refer to it. This is arguably the most important of the smart pointer classes in Boost. You should get acquainted with the other classes in Boost.Smart_ptr, too, but this one should definitely be kept close to heart. By using custom deleters, almost any type of resource can be stored in shared_ptrs. This makes shared_ptr a general class for handling resource management, rather than "just" handling dynamically allocated objects. There is a small overhead in size for shared_ptr compared to a raw pointer. I have yet to see a case where this overhead actually matters so much that another solution must be sought. Don't roll your own reference-counted smart pointer class. Instead, use shared_ptrsmart pointers don't get much better than this.

Use shared_ptr in the following scenarios:

  • When there are multiple clients of an object, but no explicit owner

  • When storing pointers in Standard Library containers

  • When passing objects to and from libraries without (other) expressed ownership

  • When managing resources that need special cleanup[9]

    [9] With the help of custom deleters.



    Beyond the C++ Standard Library(c) An Introduction to Boost
    Beyond the C++ Standard Library: An Introduction to Boost
    ISBN: 0321133544
    EAN: 2147483647
    Year: 2006
    Pages: 125

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net