Boost logo

Boost :

From: Greg Colvin (greg_at_[hidden])
Date: 2002-05-02 12:12:48


At 02:45 AM 05/02/2002, Andrei Alexandrescu wrote:
>"Phil Nash" <phil.nash.lists_at_[hidden]> wrote in message
>news:000001c1f1ab$32e848f0$700a0c0a_at_TimeMachine...
>> In which case, FWIW, I would add my voice of agreement that
>something like
>> shared_ptr could be the "standard" way to pass a shared pointer
>across a
>> published interface,
>
>I will try to convince you - and everybody - otherwise.
>
>> Andrei, put in that light does what am saying make more sense and
>sound
>> reasonable now (I think it means I was agreeing with you more than I
>thought
>> right from the start)?
>
>I don't have much time right now, and I plan to write a lenghty post
>on the subject "Binary compatibility of smart pointers: a red
>herring?" later - if ever. I am behind my sleep, and I have tons of
>homeworks and projects to worry about.
>
>I'll give some quick facts now.
>
>1. Shared pointers allow you to change at runtime the function with
>which the pointer is destroyed.

For shared_ptr, at construction time.

>The main reason, it's been said, is to allow library writers to change
>that function without having to change the binary layout of the code.

Yes.

>This is indeed the case with dynamically-linked libraries.

Dynamically linked libraries are a very important means of
distributing libraries.

>However, if
>the library is statically linked, the following construct offers the
>same capabilities, without any overhead:
>
>// Before
>shared_ptr<FILE> sp(pFile, fclose);
>// After
>shared_ptr<FILE, &fclose> sp(pFile);
>
>This would allow the implementer of FILE and fclose to change the
>implementation of fclose from a release to the next, and have the
>client code continue to work after a relink. It might be the case, but
>NOT often, that a programmer would hook into an already-existing
>shared_ptr<FILE> /at runtime/ and would change its destroyer with
>another function. I'm sure that that might be the case sometimes; but
>I argue that this is seldom the case.

shared_ptr has no such hook.

>So the situation that's helped by keeping the function pointer in the
>body of shared_ptr is dynamic linking or changing the function that
>destroys a pointer at runtime. For helping this situation, all code
>must pay. I argue that the case is not common enough to warrant
>imposing such an overhead to all libraries that want to share
>pointers.
>
>2. shared_ptr has significant size and speed overhead for COM and
>CORBA-style smart pointers

In my experience, the fastest way to handle COM locally is to do AddRef
once when constructing a shared_ptr, and Release once when counter
goes to zero, and save the virtual function calls.

>In COM, a smart pointer is as large as a regular pointer. Copying and
>destroying the pointer is as expensive as a virtual function call. COM
>objects usually do not inherit boost shared_base, and many times users
>don't have access to the source code to make this change.
>
>Adding an extra level of indirection when deallocating pointers
>roughly doubles the cost of destroying. Also, there is size overhead
>(I forgot how much).

I think copying is much more frequent than destruction.

>Refcounting COM and CORBA pointers is optimized already for proxy
>situation, so there's no reason to "reoptimize" it via another
>indirect call. That's a pessimization.
>
>I know that these are reasons enough for people I used to work with -
>and I believe for most COM (and CORBA) programmers - to stay away from
>shared_ptr for any COM work and use Microsoft's CComPtr<T> class which
>is designed for the exact task of managing COM pointers.

Yes. Since COM is pretty much Microsoft-only, there is little
reason not to use their facilities, unless the fact that something
is a COM pointer is of no interest outside of a library.

>3. I believe that having a "default smart pointer type" is not such a
>good idea.
>
>(To generalize a bit, when I hear of this notion of "default type" I
>become wary. The Standard, for example, recommends vector as the
>"default container type". What do you mean, "default"? "I don't know
>what type to choose for this variable, so let it be float". What
>gives? Oh well.)
>
>If a library has to pass around COM pointers and pointers to non-COM
>objects, in my humble opinion, it's not a good idea to use the same
>smart pointer type for both. It's just a obstination to preserve a
>situation that brings no advantage to anyone. Give to the Caesar...
>you know. Use COM-tuned pointers for COM objects; use smart pointers
>tuned for your objects, for your objects. Make them syntactically
>uniform, and make them perform well for the objects they manage.
>Provide whatever features you believe are useful. Do you want ultimate
>runtime polymorphysm? Go for a slower, runtime-flexible interface like
>shared_ptr has. Do you just want a straight external refcounting
>interface? Go for it. Do you have a faiblesse for reference linked
>pointers? That's cool, too. Make your choices, define the interfaces
>you need, and that's that.
>
>4. shared_ptr is a point in smart_ptr's design space.

It could be, yes. And vice versa.

>I might start sounding like a broker record here, but shared_ptr's
>behavior is /one/ fixed design in a rich design space offered by a
>smart pointer based on policies. I can't understand how a design with
>a hardcoded set of decisions embedded inside of it can do better and
>more than a framework that grants you that specific design all right,
>in addition to many other useful designs.

The beauty of Loki is that it IS a framework, and offers much
power and convenience to those find it an appropriate framework.

But the Standard Library, traditionally, is not so much a
framework as a collection of utilities. The framework is the
language itself, which provides the richest possible design
space. This explains much of my reluctance to embrace a
policy-based smart pointer framework for the Standard Library.
Boost is another story.

For example, consider the venerable low-level IO calls -- fopen,
fclose, fread, and so on. They represent a hard-coded set of
decisions about how to do portable, efficient, IO. They are
one point in a much richer space, but not just any point. They
suffice for most purposes, and access to the rest of the design
space is left to less portable mechanism, like direct uses of
OS-specific IO mechanisms. You could imagine a policy-based
framework for low-level IO, with choices of buffering, flushing,
blocking, file-mapping, and so on. You could even implement it
with C. But would that have been a better choice for C, or C++?

>5. Syntactic convenience
>
>This is, in my humble opinion, the last bastion of hardcoded smart
>pointer designs. As I showed in my post "Proposal for easying
>smart_ptr usage within C++98", syntactic convenience can be brought
>within acceptable bounds (in my opinion) with inner typedefs: replace
>'shared_ptr<T>' with 'ptr<T>::shared'.

Yes, this is a good idea.

Another bastion is years of experience with shared_ptr, which
indicates that it is "good enough" for most purposes, that it
can be tweaked over time to handle other important purposes,
and can provide hooks for user extension in unanticipated
ways. The syntax is easy for the most common case, easy
enough for anticipated extensions, and well within the skill
of those who would design new extentions.

>I'd love to write more on the subject, but look at the time...
>
>Andrei the sleep-deprived one

Please, get some sleep!


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