Thread: I don't get weak_ptr's?

  1. #1
    and the hat of sweating
    Join Date
    Aug 2007
    Location
    Toronto, ON
    Posts
    3,545

    I don't get weak_ptr's?

    I'm reading "Beyond the C++ Standard Library: An Introduction to Boost" and it says you'd use a weak_ptr to:
    • Break cyclic dependencies
    • Use a shared resource without sharing ownership
    • Avoid dangling pointers

    I sort of understand about breaking cyclic dependencies, although I've never written anything that could ever get in that state.
    But why would you want to use a shared resource without sharing ownership? If you plan to use it, why would you use a weak_ptr which could become NULL at any moment?
    "I am probably the laziest programmer on the planet, a fact with which anyone who has ever seen my code will agree." - esbo, 11/15/2008

    "the internet is a scary place to be thats why i dont use it much." - billet, 03/17/2010

  2. #2
    Officially An Architect brewbuck's Avatar
    Join Date
    Mar 2007
    Location
    Portland, OR
    Posts
    7,396
    Quote Originally Posted by cpjust View Post
    But why would you want to use a shared resource without sharing ownership? If you plan to use it, why would you use a weak_ptr which could become NULL at any moment?
    Suppose there are multiple components sharing the resource during the execution of an algorithm, each of which need exclusive access in a serial fashion. In this case the collection of components is really like one "supercomponent" and doesn't need independent refcounting because each component works in tandem with the others. In this case you would use weak pointers because we know that during the duration of the algorithm there is a single handle which maintains a reference on the underlying object.

    So, why not pass around bare pointers instead of weak references? The algorithm might need to acquire a new refcounted reference (for instance, to store to a separate layer of code), in other words convert a weak pointer into a strong pointer -- and in order to do this, a pointer to the actual refcount has to be available.

    So a weak pointer is the same as a bare pointer, but with the option of converting to a strong pointer when necessary.
    Code:
    //try
    //{
    	if (a) do { f( b); } while(1);
    	else   do { f(!b); } while(1);
    //}

  3. #3
    and the hat of sweating
    Join Date
    Aug 2007
    Location
    Toronto, ON
    Posts
    3,545
    OK, but then why not just pass around a shared_ptr to begin with? Is there much of a performance difference between a weak_ptr & shared_ptr?
    How often would you actually use a weak_ptr? Pretty often or very infrequently? Is it only useful for special cases that don't happen very often?
    They just seem kind of dangerous, since they can get deleted at any moment...
    "I am probably the laziest programmer on the planet, a fact with which anyone who has ever seen my code will agree." - esbo, 11/15/2008

    "the internet is a scary place to be thats why i dont use it much." - billet, 03/17/2010

  4. #4
    Officially An Architect brewbuck's Avatar
    Join Date
    Mar 2007
    Location
    Portland, OR
    Posts
    7,396
    Quote Originally Posted by cpjust View Post
    OK, but then why not just pass around a shared_ptr to begin with? Is there much of a performance difference between a weak_ptr & shared_ptr?
    Because a weak pointer doesn't increment or decrement the refcount the compiler can make a larger set of optimizations when weak pointers are assigned or copied (i.e. it might optimize to nothing). The actual act of incrementing the refcount isn't that expensive, but it changes the semantics of the code in a way that optimizes less well.

    How often would you actually use a weak_ptr? Pretty often or very infrequently? Is it only useful for special cases that don't happen very often?
    They just seem kind of dangerous, since they can get deleted at any moment...
    They are only dangerous if you don't maintain a strong pointer to the same data. Typically when a weak pointer exists, a strong pointer also exists in the same scope. So it's up to you to ensure that an actual strong reference is maintained while you pass around weak pointers.

    One example would be pointers inside some cyclic data structure. The cyclic pointers are weak, since the refcounting doesn't help anyway for cycles, and each part of the data structure is also pointed to by a strong pointer in a controller or container object -- the container maintains the lifetimes, the weak pointers are for reference only. Then the algorithm has to pass a shared pointer to some subgraph in the data structure -- it can convert the weak pointer back to a strong pointer.
    Code:
    //try
    //{
    	if (a) do { f( b); } while(1);
    	else   do { f(!b); } while(1);
    //}

  5. #5
    and the hat of sweating
    Join Date
    Aug 2007
    Location
    Toronto, ON
    Posts
    3,545
    I don't think I've ever used a cyclic data structure.
    Do you have some good examples of a cyclic data structure and when you'd use one?
    "I am probably the laziest programmer on the planet, a fact with which anyone who has ever seen my code will agree." - esbo, 11/15/2008

    "the internet is a scary place to be thats why i dont use it much." - billet, 03/17/2010

  6. #6
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    5,108
    I don't think I've ever used a cyclic data structure.
    You've never used a doubly linked list? I'm not saying that an implementation might use a shared point. I'm not even saying a particular implementation should necessarily have problems related to shared references. I'm just saying, the case where 'A' has a handle to 'B' and 'B' has a handle to 'A' creeps up more often than you might think.

    Soma

  7. #7
    and the hat of sweating
    Join Date
    Aug 2007
    Location
    Toronto, ON
    Posts
    3,545
    Quote Originally Posted by phantomotap View Post
    You've never used a doubly linked list?
    I've used a linked list that makes a straight line, but not one that creates a loop (i.e. has no beginning or end).
    "I am probably the laziest programmer on the planet, a fact with which anyone who has ever seen my code will agree." - esbo, 11/15/2008

    "the internet is a scary place to be thats why i dont use it much." - billet, 03/17/2010

  8. #8
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    5,108
    I've used a linked list that makes a straight line, but not one that creates a loop (i.e. has no beginning or end).
    O_o

    That doesn't make any difference. A circularly linked list may or may not be a doubly linked list.

    Soma

  9. #9
    The larch
    Join Date
    May 2006
    Posts
    3,573
    In a doubly linked list two nodes refer to each other. So if you were to construct it of shared_ptr's entirely it would never be freed.
    I might be wrong.

    Thank you, anon. You sure know how to recognize different types of trees from quite a long way away.
    Quoted more than 1000 times (I hope).

  10. #10
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    I just tried this out with this sample source:

    Code:
    #include <boost/shared_ptr.hpp>
    #include <iostream>
    #include <vector>
    
    template<typename T> class Node
    {
    public:
    	Node(int Id, T* Obj): pObj(Obj) { m_Id = Id; std::cout << "Node   constructing (#" << m_Id << ")...\n"; }
    	~Node() { std::cout << "Node   destructing  (#" << m_Id << ")...\n"; }
    	boost::shared_ptr< Node<T> > pNext;
    	boost::shared_ptr< Node<T> > pPrevious;
    	boost::shared_ptr< boost::shared_ptr< Node<T> > > pFirst;
    	boost::shared_ptr< boost::shared_ptr< Node<T> > > pEnd;
    	boost::shared_ptr<T> pObj; 
    
    private:
    	int m_Id;
    };
    
    class Object
    {
    public:
    	Object(int Id) { m_Id = Id; std::cout << "Object constructing (#" << m_Id << ")...\n"; }
    	~Object() { std::cout << "Object destructing  (#" << m_Id << ")...\n"; }
    
    private:
    	int m_Id;
    };
    
    int main()
    {
    	std::vector< boost::shared_ptr< Node<Object> > > v;
    	for (int i = 0; i < 10; i++)
    		v.push_back
    		(
    			boost::shared_ptr< Node<Object> >
    			( new Node<Object>(i, new Object(i)) )
    		);
    
    	v[0]->pFirst = boost::shared_ptr< boost::shared_ptr< Node<Object> > >
    		( new boost::shared_ptr< Node<Object> >(v[0]) );
    	v[0]->pEnd = boost::shared_ptr< boost::shared_ptr< Node<Object> > >
    		( new boost::shared_ptr< Node<Object> >(v[9]) );
    	v[0]->pNext = v[1];
    	
    	for (int i = 1; i <= 8; i++)
    	{
    		v[i]->pPrevious = v[i - 1];
    		v[i]->pNext = v[i + 1];
    		v[i]->pFirst = v[0]->pFirst;
    		v[i]->pEnd = v[0]->pEnd;
    	}
    
    	v[9]->pPrevious = v[8];
    	v[9]->pFirst = v[0]->pFirst;
    	v[9]->pEnd = v[0]->pEnd;
    }
    Essentially, a double-linked list with front and end markers with shared_ptrs.
    And when I run it, I get this output:

    Code:
    Object constructing (#0)...
    Node   constructing (#0)...
    Object constructing (#1)...
    Node   constructing (#1)...
    Object constructing (#2)...
    Node   constructing (#2)...
    Object constructing (#3)...
    Node   constructing (#3)...
    Object constructing (#4)...
    Node   constructing (#4)...
    Object constructing (#5)...
    Node   constructing (#5)...
    Object constructing (#6)...
    Node   constructing (#6)...
    Object constructing (#7)...
    Node   constructing (#7)...
    Object constructing (#8)...
    Node   constructing (#8)...
    Object constructing (#9)...
    Node   constructing (#9)...
    Press any key to continue . . .
    Oh, the horrors! Memory leaks all over the place!
    Last edited by Elysia; 12-07-2008 at 05:05 AM.
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  11. #11
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,895
    Actually, copying a weak_ptr modifes the weak ptr count, so it's far from free.

    So, why use a shared resource without sharing ownership? Because there are circumstances where you don't want this shared resource to stay alive. Caching comes to mind. Suppose you have a number of components that all use a big shared resource. The usage pattern is such that there's a period of high use followed by a period of no use, in every component individually.

    So, during a high usage period the component would acquire a shared_ptr to the resource and store it. Another component might also acquire the shared_ptr during the same time. When the component enters a period of no usage, it releases the shared_ptr, but keeps a weak_ptr. When the component needs the resource again, it first checks if the weak_ptr is still valid. If not, it has to reload the resource from disk.
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

  12. #12
    and the hat of sweating
    Join Date
    Aug 2007
    Location
    Toronto, ON
    Posts
    3,545
    Quote Originally Posted by CornedBee View Post
    Actually, copying a weak_ptr modifes the weak ptr count, so it's far from free.

    So, why use a shared resource without sharing ownership? Because there are circumstances where you don't want this shared resource to stay alive. Caching comes to mind. Suppose you have a number of components that all use a big shared resource. The usage pattern is such that there's a period of high use followed by a period of no use, in every component individually.

    So, during a high usage period the component would acquire a shared_ptr to the resource and store it. Another component might also acquire the shared_ptr during the same time. When the component enters a period of no usage, it releases the shared_ptr, but keeps a weak_ptr. When the component needs the resource again, it first checks if the weak_ptr is still valid. If not, it has to reload the resource from disk.
    Finally, a specific real-world example. I guess weak_ptr's would make sense in this example.
    "I am probably the laziest programmer on the planet, a fact with which anyone who has ever seen my code will agree." - esbo, 11/15/2008

    "the internet is a scary place to be thats why i dont use it much." - billet, 03/17/2010

  13. #13
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,895
    Of course, a proper resource manager would be a better way of doing this, but yeah.
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

  14. #14
    Registered User
    Join Date
    Nov 2006
    Posts
    519
    I can offer another example though I'm not sure if weak_ptr is really the best solution here...we can discuss it:


    Code:
    		boost::weak_ptr<pthread_barrier_t> barrier = 
    			mrEm.registerCallbackSynchronizationBarrier(mDynamicRegion.getSwrmId(), 
    CallbackTypesT::DESTROY, mrEm.clientId(), -1);
    
    		int err = RM_destroy_entity(mDynamicRegion.getSwrmId());
    
    		pthread_barrier_wait(barrier.lock().get());
    So some thread B wants to do some action (destroy entity) which results in a callback inside another thread A. Then B wants to synch with A at the point when the callback was received
    Such things are happening very often, so A dynamically creates a barrier if I tell to register one. Also A shall own the barrier. After A's call to pthread_barrier_wait A shall destroy the barrier so it doesn't stay around any further and can't be waitet any more by accident and so lead to deadlocks.

    So B wants a reference for its pthread_barrier_wait call, but it don't want to own ore care about the object, because this would clutter lot of client code.

    Sure, also a raw or shared_ptr would do, but it would not express the ownership relations to clearly.

Popular pages Recent additions subscribe to a feed