Boost logo

Boost :

From: Peter Dimov (pdimov_at_[hidden])
Date: 2005-02-13 16:13:37


Don G wrote:
> 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.

There are two features beyond what you listed:

1. "Do the right thing" destruction;
2. Weak pointer support.

Note that (1) does not equal "custom deleter support". Custom deleters do
not add any further overhead (when not used) over "do the right thing"
destruction.

By that I mean that shared_ptr<X> can always be destroyed when it has been
constructed properly, no matter whether at the point of destruction X is a
complete type with an accessible (and virtual, if the actual object is not
of type X) destructor, or whether 'operator delete' at the point of
destruction can deallocate from the 'operator new' heap at the point of
construction.

>> ... 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.

The theoretical minimum cost of a non-intrusive pointer is:

- per-instance pointer to the count;
- one word for the count.

Feature (1) above adds:

- vtable
- pointer

The functor need not be present when not used, although the current code
base does not do that. It can also be optimized out when it's an empty class
with the help of boost::compressed_pair. Again, the current code base
doesn't do it, but we're talking about the design-imposed overhead here.

Feature (2) adds

- an extra count.

For platforms lacking CAS (are there any?) Tyson Whitehead has posted an
implementation:

http://lists.boost.org/MailArchives/boost/msg66868.php

that locks a mutex only when weak_ptr::lock is used. If weak pointers aren't
used, the implementation has essentially the same performance as an ordinary
non-intrusive smart pointer. The mutex pool trick can be used to not include
a per-count mutex.

>> 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.

One situation where custom deleters come in handy is when you want to use
your own smart pointer in an application, but a third-party library takes a
shared_ptr. With a shared_ptr not supporting deleters, you'd be forced to
use shared_ptr in your application, too.

> 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.

You can always switch to active notification and avoid the need for a
passive observer such as weak_ptr, but this often makes your design worse,
because now the observed objects need to know about the observers in order
to notify them. There's also the 'shared from this' problem.

[...]

>> 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.

Portable code? shared_ptr doesn't need to be portable, if it comes with your
standard library.

> 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

All designs require a certain irreducible overhead, do they not? Think of it
as a std::map. The overhead of a map is not clearly specified or
articulated. (B) probably applies as well. But it works.


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk