Boost logo

Boost :

From: Ed Brey (brey_at_[hidden])
Date: 2002-07-18 09:43:09


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

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

> > Perhaps the best option would be to merge the two. Just as shared_ptr
> "auto-detects" a counted base and becomes more efficient, would it not be
> reasonable to take that to the next level so that shared_ptr elides is
> pointer-to-count member given a counted base?
> <
>
> No. shared_ptr cannot elide the pointer-to-count member under any
> circumstances, since its type doesn't change. Consider:
>
> class X
> {
> public:
>
> virtual void f() = 0;
> };
>
> class Y: public X
> {
> public:
>
> virtual void f();
> };
>
> 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?


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