Boost logo

Boost :

From: Peter Dimov (pdimov_at_[hidden])
Date: 2002-07-18 10:51:23


From: "Ed Brey" <brey_at_[hidden]>
> "Peter Dimov" <pdimov_at_[hidden]> wrote in message
news:008401c22e4a$82838470$1d00a8c0_at_pdimov2...
>
> > > > > The scenario is that there is some helper function that takes x by
> > > > intrusive pointer. The poor sap who is writing fn forgets this and
thinks
> > > > that helper is taking x by raw pointer. With shared_ptr, the
computer
> > > > catches his bug (probably a serious logic error, otherwise helper
would be
> > > > taking T by reference). With intrusive_ptr, this program compiles
fine.
> > > >
> > > > Why is this a problem?
> > >
> > > It's more of a lost opportunity than a problem. Any time the compiler
can
> > catch a bug at compile time, you have a good thing. In my example
above,
> > the program with intrusive_ptr would compile but not work. Based on
what
> > you've written below, I believe we're on the same page here.
> > <
> >
> > No, it seems that we are not. Why will this not work? Why is this a bug?
>
> I think I'm the cause of the confusion. I didn't make my example concrete
enough. I provided this code as an example:
>
> void helper(intrusive_ptr<T> x);
>
> void fn(T& x) {
> foo(&x);
> }
>
> What I was thinking, but didn't mention, was that I envisioned fn to be a
function that was designed to take a reference to any kind of object (auto,
static, or heap). I was imaging that while the author was writing fn, he
realized that "helper" would be a useful helper. But suppose he remembered
incorrectly that helper was a legacy function that took T* and didn't affect
the lifetime of x. This was the logic error I was trying to demonstrate in
my example: really helper does need to manage the lifetime of x, and so fn
should have also taken an intrusive_ptr<T>.
<

Yes, I see the point. Look at your assumptions, however.

* The author of 'helper', obviously, stores an owning reference to x
somewhere, or 'helper' wouldn't have used intrusive_ptr<T> as its argument
type. (Which makes 'helper' a misleading name at best.)

* The author of 'fn' assumes that 'helper' doesn't store a reference to its
argument.

This kind of 'misunderstanding' will inevitably lead to bugs, regardless of
the constructor explicit-ness.

> This is the example logic bug that would be caught with explicit
conversion from raw pointers, but goes undetected with implicit constrution.
To my knowledge, this kind of bug is among the reason why auto_ptr and
shared_ptr use explicit construction.
<

The reason that shared_ptr's constructor is explicit is that shared_ptr is
not designed to interoperate with raw pointers.

The _only_ safe way of constructing a shared_ptr is:

shared_ptr<X> pt(new Y);

The explicit constructor helps a bit to enforce this policy (but not much.)

intrusive_ptr is designed to interoperate freely with raw pointers.

> > "Misuse at your own risk", not "use at your own risk." It is a matter of
> > design philosophy. intrusive_ptr<T> _relies_ that objects of class T
> > interpret addref/release requests in a particular way. By using a static
> > object, or an object on the stack, with an initial reference count of
zero
> > that will 'delete this' on the last 'release', you are breaking that
> > contract.
> >
> > If you play by the rules, intrusive_ptr<> is safe, and so is the
implicit
> > conversion.
>
> This is true, but then again you could use that same argument for
anything. Don't misuse shared_ptr and it will work just fine for you too.
<

That's correct. shared_ptr is quite prone to misuse (explicit constructor
and all) unless certain rules (not enforced by the compiler) are followed.

> This same argument would indicate that intrusive_ptr should support
operator T*, since it does no harm unless it's misused.
>
> I'm not suggesting that we do this, however. I pointing out that the
question is not just a matter of "Does the class meet its end of a
well-defined contract?". The question is also, "How often do you expect the
user to accidentally break his end of the contract?" For auto_ptr, the
answer given implicit construction was assumed to be "too often". So what
is the answer for intrusive_ptr? Given general usage pattarns along the
lines of COM object pointers, I would tend to agree that the answer is "not
too often", and so we take the convenience of implicit construction.
However, if intrusive_ptr usage is largely as a leaner shared_ptr, when
perhaps the accident rate might become "too often".
<

Yes, I agree. If the accident rate that could have been prevented by an
explicit constructor becomes "too often," the constructor will have to be
made explicit.

> > class Z: public X, public counted_base
> > {
> >
> > public:
> >
> > virtual void f();
> > };
> >
> > shared_ptr<X> px(new Y); // detached count
> > shared_ptr<X> px2(new Z); // embedded count
>
> This is a good example. But what about shared_ptr<Z>? In that case
(incomplete type issue aside), the type knows that it will never need the
pointer, so why not elide it?
<

Incomplete type issue aside (although I consider incomplete type support
essential,) the only obstacle remaining is that a shared_ptr constructed
with a custom deallocator doesn't use the embedded count. :-)


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