Boost logo

Boost :

From: Don G (dongryphon_at_[hidden])
Date: 2005-02-13 13:24:02


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


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