intrusive_ptr


Header: "boost/intrusive_ptr.hpp"

intrusive_ptr is the intrusive analogue to shared_ptr. Sometimes, there's no other choice than using an intrusive, reference-counted smart pointer. The typical scenario is for code that has already been written with an internal reference counter, and where there's no time to rewrite it (or where the code's not available). Another case is when the size of a smart pointer must be exactly the size of a raw pointer, or when performance is hurt by the allocation of the reference count for shared_ptr (a rare case, I'm sure!). The only case where it would seem that an intrusive smart pointer is required, from a functional perspective, is when a member function of a pointed-to class needs to return this, such that it can be used in another smart pointer. (Actually, there are ways to solve that problem with non-intrusive smart pointers too, as we saw earlier in this chapter.) intrusive_ptr is different from the other smart pointers because it requires you to provide the reference counter that it manipulates.

When intrusive_ptr increments or decrements a reference count on a non-null pointer, it does so by making unqualified calls to the functions intrusive_ptr_add_ref and intrusive_ptr_release, respectively. These functions are responsible for making sure that the reference count is always correct and, if the reference counter drops to zero, to delete the pointer. Therefore, you must overload those functions for your type, as we shall see later.

This is a partial synopsis for intrusive_ptr, showing the most important functions.

 namespace boost {   template<class T> class intrusive_ptr {   public:     intrusive_ptr(T* p,bool add_ref=true);     intrusive_ptr(const intrusive_ptr& r);     ~intrusive_ptr();     T& operator*() const;     T* operator->() const;     T* get() const;      operator unspecified-bool-type() const;    };   template <class T> T* get_pointer(const intrusive_ptr<T>& p);    template <class T,class U> intrusive_ptr<T>     static_pointer_cast(const intrusive_ptr<U>& r);  } 

Members

 intrusive_ptr(T* p,bool add_ref=true); 

This constructor stores the pointer p in *this. If p isn't null, and if add_ref is true, the constructor makes an unqualified call to intrusive_ptr_add_ref(p). If add_ref is false, the constructor makes no call to intrusive_ptr_add_ref. This constructor can throw an exception if intrusive_ptr_add_ref can throw.

 intrusive_ptr(const intrusive_ptr& r); 

The copy constructor saves a copy of r.get() and, if that pointer is not null, calls intrusive_ptr_add_ref with it. This constructor never throws.

 ~intrusive_ptr(); 

If the stored pointer is not null, the intrusive_ptr destructor makes an unqualified call to the function intrusive_ptr_release, with the stored pointer as argument. intrusive_ptr_release is responsible for decrementing the reference count and deleting the pointer if it becomes zero. This function never throws.

 T& operator*() const; 

This dereferencing operator returns and dereferences the stored pointer. If the stored pointer is null, invoking this operator results in undefined behavior. When in doubt, make sure that the intrusive_ptr has a non-null pointer. This is done using either the function get or by testing the intrusive_ptr in a Boolean context. The dereferencing operator never throws.

 T* operator->() const; 

This operator returns the stored pointer. Calling this operator when the referenced pointer is null invokes undefined behavior. The operator never throws.

 T* get() const;  

This member function returns the stored pointer. It can be used when a raw pointer is needed, and may be called even when the stored pointer is null. This function never throws.

 operator unspecified-bool-type() const;  

This conversion function returns a type that can be used in Boolean expressions, but it is not operator bool, because that would allow for other operations that should be prohibited. The conversion allows intrusive_ptr to be tested in Boolean contextsfor example, if (p), with p being an instance of intrusive_ptr. The returned value is TRue if the intrusive_ptr references a non-null pointer; otherwise, it returns false. This conversion function never throws.

Free Functions

 template <class T> T* get_pointer(const intrusive_ptr<T>& p);  

The function returns p.get(), and its purpose is mainly to support generic programming.[10] It may also be used as a coding convention instead of calling the member function get, because it can be overloaded to work with raw pointers and third-party smart pointer classes. Some simply prefer calling a free function over accessing a member function.[11] The function never throws.

[10] Such functions are known as shims. See [12] in the Bibliography.

[11] The idea is that the line between operating on the smart pointer and operating on what it points to can be blurred when using smart pointer member functions. For example, p.get() and p->get() have completely different meanings and can be a little difficult to distinguish at a glance, whereas get_pointer(p) and p->get() look nothing alike. Whether that's a problem for you is a matter of taste and experience.

 template <class T,class U>   intrusive_ptr<T> static_pointer_cast(const intrusive_ptr<U>& r); 

This function returns intrusive_ptr<T>(static_cast<T*>(r.get())). Unlike with shared_ptr, you can use static_cast safely on pointers to objects stored in intrusive_ptrs. However, you may want to use this function for consistent usage of smart pointer casts. static_pointer_cast never throws.

Usage

There are two major differences when using intrusive_ptr compared to using shared_ptr. The first is that you need to provide the reference counting mechanism. The second is that it becomes legal to treat this as a smart pointer,[12] which can sometimes be convenient, as we shall see. Note that in most cases, the right smart pointer to use is the non-intrusive shared_ptr.

[12] You cannot do that with shared_ptr without special measures, such as enable_shared_from_this.

To use boost::intrusive_ptr, include "boost/intrusive_ptr.hpp" and then define the two free functions intrusive_ptr_add_ref and intrusive_ptr_release. These should accept an argument that is a pointer to the type(s) that you want to use with intrusive_ptr. Any return value from these two functions is discarded. Often, it makes sense to parameterize these functions, and simply forward to member functions of the managed type to do the work (for example, calling add_ref and release). If the reference counter becomes zero, intrusive_ptr_release should take care of releasing the resource. Here's how you might implement these functions generically:

 template <typename T> void intrusive_ptr_add_ref(T* t) {   t->add_ref(); } template <typename T> void intrusive_ptr_release(T* t) {   if (t->release()<=0)     delete t; } 

Note that these functions should be defined in the scope of their argument types. This means that if this function is called with arguments from a namespace, the functions should be defined there, too. The reason for this is that the calls are unqualified, which means that argument-dependent lookup is permitted, and there may be cases where more than one version of these functions must be provided, which makes the global namespace a bad place to put them. We'll see an example of where to place these functions later, but first, we need to provide some sort of internal reference counter.

Providing a Reference Counter

Now that the management functions have been defined, we must provide an internal reference count. In this example, the reference count is a private data member that's initialized to zero, and we'll expose add_ref and release member functions to manipulate it. add_ref increments the reference count and release decrements it.[13] We could add a third member function to return the current value of the reference count, but it suffices to have release return it. The following base class, reference_counter, provides a counter and the add_ref and release member functions, making adding reference counting to a class as easy as using inheritance.

[13] Note that in a multithreaded environment, any operation on the variable holding the reference count needs to be synchronized.

 class reference_counter {   int ref_count_;   public:     reference_counter() : ref_count_(0) {}        virtual ~reference_counter() {}     void add_ref() {        ++ref_count_;     }     int release() {       return --ref_count_;     }   protected:     reference_counter& operator=(const reference_counter&) {     // No-op       return *this;     }   private:     // Copy construction disallowed     reference_counter(const reference_counter&);  }; 

The reason for making the destructor of reference_counter virtual is that the class is publicly inherited, and thus it is possible to delete derived classes using a pointer to reference_counter. We want this deletion to do the right thingthat is, to call the destructor for the derived type. The implementation is straightforward: add_ref increments the reference count and release decrements the current reference count and returns it. To use this reference counter, all that's needed is to derive publicly from it. Here's an example with a class some_ class that contains an internal reference count, and intrusive_ptrs that use it.

 #include <iostream> #include "boost/intrusive_ptr.hpp" class some_class : public reference_counter { public:   some_class() {     std::cout << "some_class::some_class()\n";   }   some_class(const some_class& other) {     std::cout << "some_class(const some_class& other)\n";   }   ~some_class() {     std::cout << "some_class::~some_class()\n";   } }; int main() {   std::cout << "Before start of scope\n";   {     boost::intrusive_ptr<some_class> p1(new some_class());     boost::intrusive_ptr<some_class> p2(p1);   }   std::cout << "After end of scope \n"; } 

To demonstrate that the intrusive_ptrs together with the functions intrusive_ptr_add_ref and intrusive_ptr_release do their jobs right, here is the output from running the program:

 Before start of scope some_class::some_class() some_class::~some_class() After end of scope 

The intrusive_ptr is taking care of business for us. When the first intrusive_ptr p1 is created, it is passed a new instance of some_class. The intrusive_ptr constructor actually takes two arguments. The second is a bool that states whether intrusive_ptr_add_ref should be called or not. Because the default value of this argument is TRue, when constructing p1, the reference counter for the instance of some_class becomes 1. Then, a second intrusive_ptr, p2, is constructed. It is copy constructed from p1, and when p2 sees that p1 is referencing a non-null pointer, it calls intrusive_ptr_add_ref. The reference count is now 2. Then, the two intrusive_ptrs leave scope. First, p2 is destroyed, and the destructor calls intrusive_ptr_release. This decrements the reference counter to 1. Then, p1 is destroyed, and the destructor calls intrusive_ptr_release again, which causes the reference count to drop to 0; this in turn triggers our implementation of intrusive_ptr_release to delete the pointer. You'll note that the implementation of reference_counter is not thread-safe, and therefore cannot be used in multithreaded applications without adding synchronization.

Rather than relying on a generic implementation of intrusive_ptr_add_ref and intrusive_ptr_release, we could have these functions operate directly on the base class (here, reference_counter). The advantage of this approach is that even if the classes derived from reference_counter are defined in other namespaces, intrusive_ptr_add_ref and intrusive_ptr_release will still be found using ADL (argument dependent lookup). Changing the implementation of reference_counter is straightforward.

 class reference_counter {   int ref_count_;   public:     reference_counter() : ref_count_(0) {}        virtual ~reference_counter() {}      friend void intrusive_ptr_add_ref(reference_counter* p) {         ++p->ref_count_;      }      friend void intrusive_ptr_release(reference_counter* p) {        if (--p->ref_count_==0)          delete p;      }   protected:     reference_counter& operator=(const reference_counter&) {     // No-op       return *this;     }   private:     // Copy construction disallowed     reference_counter(const reference_counter&);  }; 

Treating this As a Smart Pointer

It's not altogether easy to come up with scenarios where intrusive, reference-counted smart pointers are really required. Most, if not all, problems can be solved with non-intrusive smart pointers. However, there is one case in which it's easier to use an intrusive reference count: when one needs to return this from a member function, to be stored in another smart pointer. When returning this from a type that's being owned by non-intrusive smart pointers, the result is that two different smart pointers believe that they own the same object, which implies that they will both try to delete it when the time has come to do so. This leads to double deletion, with the probable result that your application will crash. It must somehow be possible to tell the other smart pointer that this resource is already referenced by another smart pointer, and that's exactly what an internal reference counter (implicitly) does. Because the logic of intrusive_ptr indirectly operates on the internal reference count of the objects they refer to, there is no violation of ownership or inconsistencies in the reference counting. The reference count is simply incremented.

Let's take a look at the potential problem first, with an implementation relying on boost::shared_ptr for sharing resource ownership. It's basically the example from earlier in this chapter, when discussing enable_shared_from_this.

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

The class A wants to call the function do_stuff, but the problem is that do_stuff expects a shared_ptr<A>, not a plain pointer to A. So, in A::call_do_stuff, how should the shared_ptr be created? Now, let's rewrite A to make it compatible with intrusive_ptr, by deriving from reference_counter, and let's also add an overloaded version of do_stuff, accepting an argument of type intrusive_ptr<A>.

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

As you can see, in this version of A::call_do_stuff, we are able to send this directly to the function expecting an intrusive_ptr<A>, due to the converting constructor of intrusive_ptr.

Here's a special treat to end this section: Now that A is supporting intrusive_ptr, we can actually write code that creates a shared_ptr that wraps the intrusive_ptr, allowing us to call the original version of do_stuff, which takes a shared_ptr<A> as argument. Assuming that you cannot control the source code for do_stuff, this might be a very real problem that you need to solve. Again, the solution awaits in the form of a custom deleter, one that understands that it needs to call intrusive_ptr_release. Here's the new version of A::call_do_stuff.

 void call_do_stuff() {   intrusive_ptr_add_ref(this);   boost::shared_ptr<A> p(this,&intrusive_ptr_release<A>);   do_stuff(p); } 

An elegant solution indeed. When there are no more shared_ptrs left, the custom deleter is invoked, which calls intrusive_ptr_release, which in turn decreases the internal reference counter of A. Note that if intrusive_ptr_add_ref and intrusive_ptr_release are implemented to operate on reference_counter, you'd create the shared_ptr like so:

 boost::shared_ptr<A> p(this,&intrusive_ptr_release); 

Supporting Different Reference Counters

We talked earlier about the possibility of supporting different reference counts for different types. This may be necessary when integrating existing classes with different reference-counting mechanisms (third-party classes employing their own version of a reference-counter, for example). Or there may be different requirements for deallocating, such as calling another function rather than delete. As mentioned already, the calls to intrusive_ptr_add_ref and intrusive_ptr_release are unqualified. This means that the scope of the argument (the pointer type) is considered during name lookup, and thus these functions should be defined in the same scope as the type on which they should operate. If you implement generic versions of intrusive_ptr_add_ref and intrusive_ptr_release in the global namespace, you make it impossible to create generic versions in other namespaces. For example, if a namespace needs a special version for all of its types, specializations or overloads must be provided for each and every type. Otherwise, the functions in the global namespace introduce an ambiguity. It is therefore not a good idea to provide generic versions in the global namespace, though they are fine in other namespaces.

Because of the way that we have implemented the reference counter, using the base class reference_counter, it is a good idea to have an ordinary function in the global namespace that accepts an argument of type reference_counter*. This still allows us to provide generic overloads inside other namespaces without introducing ambiguities. As an example, consider the classes another_class and derived_class in a namespace called my_namespace.

 namespace my_namespace {   class another_class : public reference_counter {   public:     void call_before_destruction() const {       std::cout <<          "Yes, I'm ready before destruction\n";     }   };   class derived_class : public another_class {};    template <typename T> void intrusive_ptr_add_ref(T* t) {      t->add_ref();    }   template <typename T> void intrusive_ptr_release(T* t) {     if (t->release()<=0) {       t->call_before_destruction();       delete t;     }   } } 

Here, we have implemented generic versions of intrusive_ptr_add_ref and intrusive_ptr_release. We must therefore remove the generic versions that we previously put in the global namespace, and replace them with non-templated versions accepting a pointer to reference_counter as their argument. Or, we could omit these functions from the global namespace altogether, to avoid cluttering it. For the two classes my_namespace::another_class and my_namespace::derived_class, the special version (which calls a member function call_before_destruction on its arguments) is called. Other types either have corresponding functions in the namespace where they are defined or use the version in the global namespace, if it exists. Here's a short program to illustrate how this works:

 int main() {   boost::intrusive_ptr<my_namespace::another_class>      p1(new my_namespace::another_class());   boost::intrusive_ptr<A>      p2(new good_class());   boost::intrusive_ptr<my_namespace::derived_class>      p3(new my_namespace::derived_class()); } 

First, the intrusive_ptr p1 is passed a new instance of my_namespace:: another_class. When resolving the call to intrusive_ptr_add_ref, the compiler finds the version in my_namespace, the namespace of the my_namespace:: another_class* argument. Thus, the generic function, which is provided for types in that namespace, is correctly invoked. This applies when finding intrusive_ptr_release, too. Then, the intrusive_ptr p2 is created and passed a pointer of type A (the one we created earlier). That type resides in the global namespace, so when the compiler tries to find the best match for the call to intrusive_ptr_add_ref, it finds only one, which is the version accepting an argument of type pointer to reference_counter (you'll recall that we had to remove the generic version from the global namespace). Because A inherits publicly from reference_counter, the implicit conversion succeeds and the correct call is made. Finally, the generic version in my_namespace is used for the class my_namespace::derived_class; this works exactly like the lookup for another_class.

The important lesson here is that when implementing the function intrusive_ptr_add_ref and intrusive_ptr_release, they should always be defined in the namespace where the types they operate on exist. This makes perfect sense from a design perspective too, keeping related things together, and it helps ensure that the correct version is always called, regardless of whether there are several different implementations to choose from.

Summary

In most situations, you should not use boost::intrusive_ptr, because the functionality of shared ownership is readily available in boost::shared_ptr, and a non-intrusive smart pointer is more flexible than an intrusive smart pointer. However, there are times when one needs an intrusive reference count, perhaps for legacy code or for integration with third-party classes. When the need arises, intrusive_ptr fits the bill, with the same semantics as the other Boost smart pointer classes. By using another of the Boost smart pointers, you ensure a consistent interface for all smart pointer needs, be they intrusive or not. The reference count must be provided by the classes that are used with intrusive_ptr. intrusive_ptr manages the reference count by making unqualified calls to two functions, intrusive_ptr_add_ref and intrusive_ptr_release; these functions must properly manipulate the intrusive reference count for intrusive_ptrs to work correctly. For all cases where a reference count already exists in the types that are to be used with intrusive_ptr, enabling support for intrusive_ ptr is as easy as implementing those two functions. In some cases, it's possible to create parameterized versions of those functions, and have all types with intrusive reference counts use the same implementation of these functions. In most cases, the best place to declare the functions is in the same namespace as the types they support.

Use intrusive_ptr when

  • You need to treat this as a smart pointer.

  • There is existing code that uses or provides an intrusive reference count.

  • It is imperative that the size of the smart pointer equals the size of a raw pointer.



    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