To boost::shared_ptr or not to boost::shared_ptr

Started by
32 comments, last by Red Ant 15 years, 9 months ago
Quote:Original post by asp_
Quote:
I understand that if thread A reduces the counter to 0 and thread B tries to access the object while it is being destroyed by thread A. Is that the only problem or can the reference count in the shared_ptr actually screw up and either create a resource leak or prematurely reduce the reference count to 0?

This shouldn't really ever happen. Generally speaking there is no race condition for the reference counter as the increase and decrease operation is implemented in an atomic fashion.


Says who?

There is no requirement that the reference count be implemented in an atomic fashion. For example the implementation of boost::shared_pointer explicitly doesn't synchronize the reference count. What reason do you have to believe that std::tr1::shared_ptr does?

Since boost::shared_ptr and std::tr1::shared_ptr are implementations of the same specification (see: TR1), we can say with certainty that atomicity is not a requirement.

As I said before, you can overcome this by simply using boost::intrusive_ptr and implementing your own synchronized reference counter.

Quote:Original post by asp_
The reset function however probably isn't protected as it operates on the object pointer and on the reference counter pointer.


It would be sufficient to synchronize the reference count alone.
Advertisement
Quote:Original post by fpsgamer
// Custom deleter function objectstruct CustomDeleter{     operator()(FILE* fp)     {        fclose(fp);     }};...// Create a shared_ptr representing a resourceshared_ptr<FILE> myFileHandle( fopen("MyFile.txt", "r"), CustomDeleter());



You don't even need to go as far as that;

shared_ptr<FILE> file(fopen("MyFile.txt", "r"), fclose);

I've used that before when having to deal with FILE* and libpng to avoid worrying about clean up [smile]


Quote:
There is no requirement that the reference count be implemented in an atomic fashion. For example the implementation of boost::shared_pointer explicitly doesn't synchronize the reference count. What reason do you have to believe that std::tr1::shared_ptr does?

I never said anything about TR1. I specifically said to examine the detail space of boost. Just because you couldn't look it up yourself I've done it for you:

boost/shared_ptr.hppmember of shared_ptrboost::detail::shared_count pn;    // reference counter


boost/detail/shared_count.hppmember of shared_countsp_counted_base * pi_;


boost/detail/sp_counted_base.hppincludes sp_counted_base_w32.hpp


boost/detail/sp_counted_base_w32.hppmember of sp_counted_base    long use_count_;        // #shared    long weak_count_;       // #weak + (#shared != 0)    void release() // nothrow    {        if( BOOST_INTERLOCKED_DECREMENT( &use_count_ ) == 0 )        {            dispose();            weak_release();        }    }


boost/detail/interlocked.hpp# include <windows.h># define BOOST_INTERLOCKED_INCREMENT InterlockedIncrement


Oh my god. And look the GCC version does it as well.. and every other version... Do you realize how completely useless the shared_ptr would be without atomic reference count updates? Asynchronous operations would be completely impossible under more than one thread. Also synchronizing the count alone would clearly not be sufficient when you have a reset function, the document that in the example you posted.
Woah guys, no need to fight! :)

Thank you for your input and I do have a better understanding now. I like raw pointers, but if I can get used to using shared_ptr (in mt environment) and not be penalized with speed much, I think I'll be switching over.
Quote:Original post by loufoque
shared ownership, which shouldn't even happen in a normal program, unless as a low-level implicit sharing optimization.


This is some kind of joke, right? You are aware, I hope, that many *very high* level languages share objects *all over the place, automatically*? It's called "reference semantics", and they're very, very useful. Of course, built-in garbage collection helps a lot with that kind of system :)
Quote:Original post by asp_
I never said anything about TR1.


The OP is specifically referring to TR1. Thats why I mentioned it.

Quote:Original post by asp_
I specifically said to examine the detail space of boost. Just because you couldn't look it up yourself I've done it for you:


Thats needlessly obnoxious, and yes I did look it up.

You've unfortunately misunderstood the synchronization guarantees made by boost::shared_ptr. boost::shared_ptr is distinctly lock-free as evidenced by the following quote:

"Starting with Boost release 1.33.0, shared_ptr uses a lock-free implementation on the following platforms:

* GNU GCC on x86 or x86-64;
* GNU GCC on IA64;
* Metrowerks CodeWarrior on PowerPC;
* GNU GCC on PowerPC;
* Windows."


The problem is that the synchronization methods employed do not provide mutual exclusion for both reads and writes.

Quote:Original post by asp_
Oh my god. And look the GCC version does it as well.. and every other version... Do you realize how completely useless the shared_ptr would be without atomic reference count updates? Asynchronous operations would be completely impossible under more than one thread.


In spite of lack of mutual exclusion, you can use shared_ptr on multiple threads under certain conditions:

  • shared_ptr objects offer the same level of thread safety as built-in types. A shared_ptr instance can be "read" (accessed using only const operations) simultaneously by multiple threads.


  • Different shared_ptr instances can be "written to" (accessed using mutable operations such as operator= or reset) simultaneosly by multiple threads (even when these instances are copies, and share the same reference count underneath.)


  • Any other simultaneous accesses result in undefined behavior

eg.
//--- Example 4 ---// thread Ap3 = p2; // reads p2, writes p3// thread B// p2 goes out of scope: undefined, the destructor is considered a "write access"


If you read the documentation, things will be clear. And for the last time: if you need synchronized reference counts use intrusive_ptr.
Any reference count synchronization problems with shared_ptr can easily be overcome by giving each thread its own shared_ptr instance. That way you're practically guaranteed that the reference count is always correct and the d'tor of the shared object is only run once and only by one thread.
Quote:Original post by Red Ant
Any reference count synchronization problems with shared_ptr can easily be overcome by giving each thread its own shared_ptr instance. That way you're practically guaranteed that the reference count is always correct and the d'tor of the shared object is only run once and only by one thread.


Not quite. Counter example:

Suppose you have three threads, the main thread, thread A and thread B.

The main thread has the original shared_ptr. Threads A and B have copies of that shared_ptr.

Lets say thread A passes the shared_ptr to another function by value (read/write access) when thread B terminates (write access).

Voila, undefined behavior.
Quote:Original post by loufoque
IMO shared_ptr is pure evil and should not be used.
It is only to manage shared ownership, which shouldn't even happen in a normal program, unless as a low-level implicit sharing optimization.
A good C++ program shouldn't use pointers at all, but only values and weak references.


Wrong on multiple levels.

1) Pointers are a necessary component in the implementation of containers, and while the standard library ones often suffice, there are times when a custom implementation is required.

2) Further, the nature of C++'s references are such that they cannot be reseated, pointers are acceptable as a reseatable alternative in the case that this is necessary.

3) Unlike builtin constructs, shared_ptr, when used in conjunction with weak_ptr, infers much greater safety when it comes to weak references, allowing you to have defined behavior when the rug is pulled out from underneath some part of the program.

4) While clearly defined ownership is all fine and good, shared_ptr is great for the situations where clearly defined ownership is either inappropriate or costly in implementation and mantinence (which the principle of KISS says is inappropriate, getting back to the original point).
Quote:Original post by fpsgamer
Lets say thread A passes the shared_ptr to another function by value (read/write access) when thread B terminates (write access).

Voila, undefined behavior.


I don't understand. How's that undefined? Thread A essentially copy constructs a new shared_ptr instance from an existing one while thread B runs the d'tor of its shared_ptr instance. All accesses to the underlying reference count are properly synchronized, and when all is said and done the reference count is still going to be consistent.

This topic is closed to new replies.

Advertisement