weak_ptr


Header: "boost/weak_ptr.hpp"

A weak_ptr is an observer of a shared_ptr. It does not interfere with the ownership of what a shared_ptr shares. When a shared_ptr that is being observed by a weak_ptr must release its resource, it sets the observing weak_ptr's pointer to null. That prevents the weak_ptr from holding a dangling pointer. Why would you need a weak_ptr? There are many situations where one needs to observe and use a shared resource without accepting ownership, such as to break cyclic dependencies, to observe a shared resource without assuming ownership of it, or to avoid dangling pointers. It's possible to construct a shared_ptr from a weak_ptr, thereby gaining access to the shared resource.

This is a partial synopsis for weak_ptr, showing and then briefly discussing the most important functions.

 namespace boost {   template<typename T> class weak_ptr {   public:     template <typename Y>       weak_ptr(const shared_ptr<Y>& r);     weak_ptr(const weak_ptr& r);     ~weak_ptr();     T* get() const;      bool expired() const;      shared_ptr<T> lock() const;   };   }  

Members

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

This constructor creates a weak_ptr from a shared_ptr, provided there is an implicit conversion from Y* to T*. The new weak_ptr is configured to observe the resource referred to by r. r's reference count remains unchanged. This implies that the resource referenced by r may be deleted despite the existence of the new weak_ptr referring to it. This constructor never throws.

 weak_ptr(const weak_ptr& r);  

The copy constructor makes the new weak_ptr observe the resource referenced by shared_ptr r. The reference count of the shared_ptr is unchanged. This constructor never throws.

 ~weak_ptr(); 

The weak_ptr destructor, similarly to the constructor, does not change the reference count. If needed, the destructor detaches *this as an observer for the shared resource. This destructor never throws.

 bool expired() const; 

Returns TRue if the observed resource has "expired," which means that it has been released. If the stored pointer is non-null, expired returns false. This function never throws.

 shared_ptr<T> lock() const 

Returns a shared_ptr that refers to the resource that this weak_ptr observes, if any. If there is no such pointer (that is, the weak_ptr refers to the null pointer), the resulting shared_ptr refers to the null pointer. Otherwise, the reference count for the resource referenced by the shared_ptr is incremented as usual. This function never throws.

Usage

We begin with an example that shows the basics of weak_ptrs, and especially demonstrates how they do not affect the reference counts. Out of necessity, the examples in this subsection include shared_ptrs as well, because a weak_ptr always needs to be used together with a shared_ptr. To use weak_ptr, include "boost/weak_ptr.hpp".

 #include "boost/shared_ptr.hpp" #include "boost/weak_ptr.hpp" #include <iostream> #include <cassert> class A {}; int main() {   boost::weak_ptr<A> w;   assert(w.expired());   {     boost::shared_ptr<A> p(new A());     assert(p.use_count()==1);     w=p;     assert(p.use_count()==w.use_count());     assert(p.use_count()==1);     // Create a shared_ptr from the weak_ptr     boost::shared_ptr<A> p2(w);     assert(p2==p);   }   assert(w.expired());   boost::shared_ptr<A> p3=w.lock();   assert(!p3); } 

The weak_ptr w is default constructed, which means that it initially isn't observing any resource. To test whether or not a weak_ptr is observing a live object, you use the function expired. To start observing, a weak_ptr must be assigned a shared_ptr. In the example, when shared_ptr p is assigned to weak_ptr w, it's asserted that the use countsthat is, the reference countof p and w are equal. Then, a shared_ptr is constructed from the weak_ptr, which is one of the ways to gain access to the shared resource from a weak_ptr. If the weak_ptr has expired when the shared_ptr is constructed, an exception of type boost::bad_weak_ptr is thrown by shared_ptr's constructor. Moving on, when the shared_ptr p goes out of scope, w is expired. When its member function lock is called to obtain a shared_ptrthe second way of gaining access to the shared resourcean empty shared_ptr is returned. Note that throughout the example, the weak_ptr had no effect on the reference count of the shared object.

Unlike other smart pointers, weak_ptr doesn't provide access to the observed pointer with overloaded operator* and operator->. The reason for this is that all operations on the resource that the weak_ptr is observing must explicitly be made safe; it would be just too easy to inadvertently access an invalid pointer, as weak_ptrs do not affect the reference counter of the shared resource they observe. This is why you must pass the weak_ptr to shared_ptr's constructor, or obtain a shared_ptr by calling weak_ptr::lock. Both of these operations increase the reference count, so after the shared_ptr is created from a weak_ptr, it keeps the shared resource alive, ensuring that it will not be deallocated during the time we want to use it.

A Common Question

Because the ordering of smart pointers doesn't involve the actual values their pointers are pointing to but the pointer values, a common question with regard to using these smart pointers in Standard Library containers is how to use algorithms with the smart pointers; they typically need to access the values of the actual objects rather than their addresses. For example, how does one call std::sort and make it perform useful sorting? Actually, the problem is hardly any different from storing and operating on regular pointers in containers, but that fact is easily overlooked (probably because storing a raw pointer is so problematic that we tend to avoid doing it). Out of the box, there is no support for comparing the values of smart pointers, but that's easy to amend. A typical need is to use predicates that dereference the smart pointers, so we'll create a reusable predicate that makes it easy to use the Standard Library algorithms with iterators referencing smart pointersin this case, weak_ptrs.

 #include <functional> #include "boost/shared_ptr.hpp" #include "boost/weak_ptr.hpp" template <typename Func, typename T>    struct weak_ptr_unary_t :      public std::unary_function<boost::weak_ptr<T>,bool> {   T t_;   Func func_;   weak_ptr_unary_t(const Func& func,const T& t)      : t_(t),func_(func) {}   bool operator()(boost::weak_ptr<T> arg) const {     boost::shared_ptr<T> sp=arg.lock();     if (!sp) {       return false;     }     return func_(*sp,t_);   } }; template <typename Func, typename T> weak_ptr_unary_t<Func,T>    weak_ptr_unary(const Func& func, const T& value) {     return weak_ptr_unary_t<Func,T>(func,value); } 

The weak_ptr_unary_t function object is parameterized on the function to invoke, and the type of the argument. The fact that the function to invoke is stored in the function object makes the function object easy to use, which we shall see shortly. To make the predicate compatible with adapters, weak_ptr_unary_t derives from std::unary_function, which makes sure that the required typedefs are present (this is required in order for the Standard Library adaptors to work with such function objects). The real work is done in the function call operator, where a shared_ptr is created from the weak_ptr. This is necessary to ensure that the resource stays alive during the function call. Then, the function (or function object) is invoked, passing the argument (dereferenced, so that we get to the actual resource) and the stored value, which was passed in the constructor for weak_ptr_unary_t. This simple function object can now be used with any applicable algorithms. For convenience, we also defined a helper function, weak_ptr_unary, which deduces the types of the arguments and returns an appropriate function object.[14] Let's see how we can use this beast.

[14] To make this type generally usable, a lot more programming would be required.

 #include <iostream> #include <string> #include <vector> #include <algorithm> #include "boost/shared_ptr.hpp" #include "boost/weak_ptr.hpp" int main() {   using std::string;   using std::vector;   using boost::shared_ptr;   using boost::weak_ptr;   vector<weak_ptr<string> > vec;   shared_ptr<string> sp1(     new string("An example"));   shared_ptr<string> sp2(     new string("of using"));   shared_ptr<string> sp3(     new string("smart pointers and predicates"));   vec.push_back(weak_ptr<string>(sp1));   vec.push_back(weak_ptr<string>(sp2));   vec.push_back(weak_ptr<string>(sp3));   vector<weak_ptr<string> >::iterator     it=std::find_if(vec.begin(),vec.end(),      weak_ptr_unary(std::equal_to<string>(),string("of using")));   if (it!=vec.end()) {     shared_ptr<string> sp(*++it);     std::cout << *sp << '\n';   } } 

In the example, a vector containing weak_ptrs is created. The most interesting line of code (yes, it's quite a long one) is where we create a weak_ptr_unary_t for use with the find_if algorithm.

 vector<weak_ptr<string> >::iterator it=std::find_if(   vec.begin(),   vec.end(),   weak_ptr_unary(     std::equal_to<string>(),string("of using"))); 

The function object is created by passing another function object, std::equal_to, to the helper function weak_ptr_unary, together with the string that is to be used for the matching. Because of the fact that weak_ptr_unary_t is compatible with adaptors (it is compatible because it inherits from std::unary_function), we could compose any type of function object out of it. For instance, we could have searched for the first string not matching "of using":

 vector<weak_ptr<string> >::iterator it=std::find_if(   vec.begin(),   vec.end(), std::not1(     weak_ptr_unary(       std::equal_to<string>(),string("of using")))); 

The Boost smart pointers were specifically designed to work well with the Standard Library. That makes it easy for us to create useful components that help us simplify the usage of these powerful smart pointers. Utilities such as weak_ptr_unary aren't needed all that often; there are libraries that provide general binders that do a much better job of that than weak_ptr_unary.[15] These, too, are typically aware of smart pointer semantics, which makes using them completely transparent to use.

Two Idiomatic Ways of Creating a shared_ptr from a weak_ptr

As you have seen, when you have a weak_ptr that's observing some resource, you'll eventually want to access that resource. To do so, the weak_ptr must be converted to a shared_ptr, because the weak_ptr alone does not allow direct access to the resource. There are two ways of creating a shared_ptr from a weak_ptr: Either pass the weak_ptr to the constructor of shared_ptr or call the weak_ptr member function lock, which returns a shared_ptr. Which to choose depends on whether you consider an empty weak_ptr to be an error or not. The shared_ptr constructor accepting a weak_ptr argument will throw an exception of type bad_weak_ptr if the weak_ptr is empty. It should therefore be used only if an empty weak_ptr constitutes an error. When using the weak_ptr function lock, the returned shared_ptr will be empty if the weak_ptr is empty. This is the right thing to do if you need to test for a valid resourcethat is, an empty weak_ptr is expected behavior. Furthermore, when using lock, the idiomatic way to use the resource is to initialize it and test it simultaneously, like so:

 #include <iostream> #include <string> #include "boost/shared_ptr.hpp" #include "boost/weak_ptr.hpp" int main() {   boost::shared_ptr<std::string>      sp(new std::string("Some resource"));   boost::weak_ptr<std::string> wp(sp);   // ...   if (boost::shared_ptr<std::string> p=wp.lock())     std::cout << "Got it: " << *p << '\n';   else     std::cout << "Nah, the shared_ptr is empty\n"; } 

As you can see, the shared_ptr p is initialized with the result of locking the weak_ptr wp. Then p is tested, and only if it is non-empty is the resource accessed. As the shared_ptr is only valid in that scope, there is no chance of inadvertently trying to use it outside of the scope where it is valid. The other scenario is when the weak_ptr logically must be non-empty. In that case, testing for an empty shared_ptr is easy to forgetand because the shared_ptr constructor throws an exception when handed an empty weak_ptr, this is the way to go.

 #include <iostream> #include <string> #include "boost/shared_ptr.hpp" #include "boost/weak_ptr.hpp" void access_the_resource(boost::weak_ptr<std::string> wp) {   boost::shared_ptr<std::string> sp(wp);   std::cout << *sp << '\n'; } int main() {   boost::shared_ptr<std::string>      sp(new std::string("Some resource"));   boost::weak_ptr<std::string> wp(sp);   // ...   access_the_resource(wp);   } 

In this example, the function access_the_resource constructs the shared_ptr sp from a weak_ptr. It doesn't need to test whether the shared_ptr is empty or not, because if the weak_ptr is empty, an exception of type bad_weak_ptr is thrown, and therefore the function leaves scope immediately; catching and handling the error will be handled where it's suitable. This is much better than explicitly testing for an empty shared_ptr and then returning. These are the two ways in which to get a shared_ptr out of a weak_ptr.

Summary

weak_ptr is the last piece that we must place on the Boost smart pointer puzzle. The weak_ptr abstraction is a very important companion to that of shared_ptr. It allows us to break cyclic dependencies. It also handles a very common problemthat of the dangling pointer. When sharing a resource, it is common that some of the users of that resource must not take part in its lifetime management. This cannot be handled using raw pointers, because when the last shared_ptr is destroyed, it takes the shared resource with it. Had raw pointers been used to refer to that resource, there would be no way of knowing whether the resource still exists. If it doesn't, accessing it wreaks havoc. With weak_ptrs, the information that the shared resource has been destroyed is propagated to all weak_ptrs observing it, which means that no one can inadvertently access an invalid pointer. It's like a special case of the Observer pattern; when the resource is destroyed, those who have expressed interest in knowing about it are informed.

Use weak_ptr to

  • Break cyclic dependencies

  • Use a shared resource without sharing ownership

  • Avoid dangling pointers



    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