
Hi Peter, Sorry it took so long for me to respond (real life just keeps getting in the way), and thanks for taking the time to reply.
shared_ptr only contains the features that are absolutely essential for the answer of "is shared_ptr suitable" to be "yes" in the vast majority of cases (and to stay "yes" as the design evolves).
I guess this is the crux of the my contention. I think all must agree there must be: automatic counting; basic pointer syntax (including any implicit conversion from derived to base, and various operators). The features beyond those are of the debatable variety.
A not so small number of extra features that do not contribute significantly to its expressive power have been rejected.
I can only imagine! :)
I suspect that you haven't had much experience with the design possibilities opened by weak pointers or custom deleters ...
I have not; shared_ptr et.al. is new to me. My own work never followed those paths for two reasons. The custom deleter never occured to me because I got the same effect with intrusion. The weak_ptr role was filled by T* or T&, and the standard refrain of "be careful what you are doing if you go this way." Of course, in most cases one can do some refactoring and remove the need for the weak reference. ...
which might be why you don't want to pay 40+ bytes for them. For me, they are easily worth double this cost.
I would agree _if_ the costs were incurred when and where I used those features. My issue is the relatively high cost of shared_ptr even when I don't use its advanced features. Primarily, this cost comes from the deleter, but weak_ptr and its mutex are just about as bad if not worse in multi-threaded applications.
That said, Alexander Terekhov has discovered a lock-free algorithm that implements the reference counting required by shared_ptr, ...
That is good news, and yes, he is more clever than I. The solution is most welcome, because that reduces the cost of weak_ptr to a single extra word. Sadly, it must be present even when weak_ptr is never used, but the cost is really low. Much better than having a mutex, even of the lightweight variety. Well, at least for platforms that support CAS or similar lock-free primitives needed to write such an algorithm.
... so this brings down the design-imposed overhead to four words per object for an empty deleter.
From 1.32 code, shared_ptr never has an empty deleter unless it is itself empty (or NULL). Unless I missed something? If my analysis is correct, an overhead of 4 words is required for the deleter alone:
- shared_count must contain a pointer to the sp_counted_base - sp_counted_base has a vtable - sp_counted_base has a pointer - sp_counted_base has a functor The weak_ptr adds an extra count (1 word) but more importantly requires a mutex, at least on platforms lacking more advanced lock-free primitives such as InterlockedCompareExchange. So, where lock-free weak_ptr can be achieved, the overhead (beyond the minimum of a single word for the reference count) is 5 words, plus any heap allocation overhead. Where it cannot, we are still at 5 words plus sizeof(lightweight_mutex). If it were possible to only have a custom deleter if the user requested one, that would indeed drop the cost to 2 extra words. This would require that the counts be moved from sp_counted_base into shared_count and shared_ptr to contain a shared_count*. While I have a harder time seeing the value here vs. weak_ptr, at least its only 1 word/object "wasted". I suspect this approach would reduce the safety guarantees you were after with respect to shared_ptr<void> and/or the lack of virtual dtors. To me, the gratuitous virtual dtor is undesirable, but shared_ptr<void> has some appeal. Perhaps shared_ptr<void> could be specialized to be the only form that always creates the deleter (if not already present)? Unfortunately, a round-trip through shared_ptr<void> would likely keep the deleter, but at least the cost would only be there when shared_ptr<void> were used.
You are absolutely correct that sometimes you can get away with a simpler smart pointer that carries less overhead. The emphasis in shared_ptr's case is on correctness and expressive power over efficiency, because (IMO) the first two provide more value in a standard smart pointer, since they are much more harder to reinvent.
I think "sometimes" is a bit of an understatement: you can always "get away with" a simpler smart pointer; there are just tradeoffs to be made. In many cases, a simple smart pointer can lead to a better design (IMO). Frequently when I wanted to avoid a weak reference, the refactoring was an overall improvement. This wouldn't always be the case, of course. As always, a balance point must be found. A garbage collector is safer and probably more expressive. Most C++ folks don't like the associated cost of that solution. My fear is that an overly complex design, with the overhead it entails, will put shared_ptr in that boat for more applications than you might think.
The good thing is that once you have a stable design, efficiency usually follows, as happened here.
True enough, but optimizations leveraging advanced, platform-specific capabilities do not offer much benefit to those writing portable code. For them, the cost remains the same. In this case, this will always be so. Now we've come back to my original point: the very design of shared_ptr requires a certain overhead that is _irreducible_ in the general case. And that overhead is: A. Not clearly specified or articulated (for those who need to know) B. _Much_ more than one would naively expect Best regards, Don ===== __________________________________ Do you Yahoo!? Yahoo! Mail - Find what you need with new enhanced search. http://info.mail.yahoo.com/mail_250