From: Andrei Alexandrescu (andrewalex_at_[hidden])
Date: 2002-04-30 17:46:24
"Greg Colvin" <greg_at_[hidden]> wrote in message
> So for boost::shared_ptr we can estimate that the cost used to be
> 2 words per pointer plus 1 word per object, and now it is 2 words
> per pointer plus 3 words per object. Clearly, the more objects
> are shared per object, and the bigger the shared objects, the less
> difference that extra two words makes to the overhead. So, for
> example, if you have 100 byte objects, and average 10 pointers per
> object, the overhead for supporting weak pointer is only .001 words
> per pointer, i.e. 1 tenth of 1 percent.
> So the question is, what is the distribution of the numbers of
> shared_ptr per object? And what is the distribution of the size of
> objects? And how much overhead is too much? My take is that the
> question of whether to support weak_ptr by default in the standard
> library comes down to how useful the feature is more than to how
> much overhead is creates.
> For boost, we have already made that choice, but recently enough
> that we haven't gotten much feedback on whether the cost is OK.
A thorny problem in designing libraries is that you never know exactly how
they're going to be used. So you should do your best in balancing tradeoffs
between ease of use, safety, and, yes, efficiency. Furthermore, when
designing a library, as well as when designing a language, it is very easy
to go amok in a direction while losing the big picture. Balance is
One observation to make is that efficiency is like friction: you never can
take it back. This phenomenon is especially visible when abstractions that
are built out of their concrete incarnations, are often less efficient when
taken back to the same incarnations they started from. So an important thing
to remember is, you can (at least theoretically) build a safe, highly
abstract, and/or easy to use library, on top of an efficient one - but
definitely not vice versa.
Each hardcoded design decision is like a certain move in the design space.
That move incurs friction, and you can't take it back. A particular usage
pattern of a library might not enjoy the point where the library design
moved, so the application would move it to another point, thus causing again
friction. But on the other hand, if the library doesn't establish some
structure to the design space, then it didn't help the application at all,
because then there's too little to rely on, too little to reuse. This is the
fundamental tension in library design.
Policy-based design (and similar techniques) tries to address this problem
by giving some structure to the design space, but letting the user make the
move. So instead of a point, the library offers multiple reachable points in
the design space, and furthermore the user can define new points within
certain limited directions. This way, the library does give reuse benefits,
while letting the user call the shots.
Smart pointers are a low-level abstraction. The cost of that abstraction
ought to be minimal so that larger and larger structures are built on top of
it. In light of all said above, a worthwhile goal in designing a generic
smart pointer is to make it an efficient building block so that other
abstractions can be built on top of it without much "friction".
Of course, a design might reach a point where it's simply too advantageous
to give up some efficiency in exchange for an exceptionally better
abstraction. Actually, revolutionary steps have been made exactly when some
penalty was accepted in favor of a conceptual leap. That's for example how
Fortran, Unix, or microkernels were born, and examples can continue.
I argue that this is not the case for smart pointers. A smart pointer can be
designed so that it gets rid of the extra storage except when needed, and
does not lose in the abstraction domain at all. Therefore, why not using the
better design, if efficiency also comes as a fringe benefit of it?