Boost logo

Boost :

From: David B. Held (dheld_at_[hidden])
Date: 2003-01-30 16:41:03


"David Abrahams" <dave_at_[hidden]> wrote in message
news:u4r7qe5gb.fsf_at_boost-consulting.com...
> "David B. Held" <dheld_at_[hidden]> writes:
> > [...]
> > smart_ptr(P p)
> > : storage(p), ownership(p), checking(), conversion()
> > { checking::on_init(p) }
> >
> > 1) storage(p) throws
>
> You missed "copying P throws", unless you have prohibited that
> somewhere. Is P always a pointer type?

Will you cut that out?? You're really beginning to annoy me! ;> Lucky
for me this time, it was just a case of laziness. The real code is this:

        template <typename U>
        smart_ptr(U const& p)
        : base_type(p, detail::init_first_tag())
        { checking_policy::on_init(get_impl(*this)); }

But that's a lot less readable for someone not intimately familiar with
the implementation. However, I should have specified P const&.

> > [...]
> > storage(p) must have succeeded, so it is a fully c'ted subobject.
> > ownership is responsible for cleaning up its own resources, but
> > nothing else. ~storage() will get called, cleaning up p.
>
> Precisely what happens when p is "cleaned up?"

Under the old implementation, "cleaning up p" was equivalent to
calling storage_policy::destroy(). Now, it's equivalent to calling
storage_policy::~storage_policy().

> IIRC the cleanup of p was supposed to be cooperative between
> storage and ownership.

Actually, ownership just decides *when* to clean up. In this
particular case, we don't need to consult ownership, because we
are taking possession of a resource that is assumed to be
unmanaged. Thus, we are free to clean it up with extreme
prejudice.

> At this point no ownership object is available. Is "cleanup" always
> the same sequence of operations, using a static member of
> ownership to avoid needing object state?

Ownership may have state, but but it need only be consulted when
the resource could possibly be shared or moved. That's only
possible if the smart_ptr c'tor has completed. If the resource is
not yet shared or moved, then we own it exclusively, and can
destroy it at will.

> [...]
> > Let's fix ref_counted:
> >
> > ~ref_counted()
> > {
> > delete pCount_;
> > }
> >
> > bool release(P const&)
> > {
> > if (!--*pCount_) return true;
> > pCount_ = 0;
> > return false;
> > }
> >
> > Ok, now ownership will properly clean up after itself.
>
> That is not completely clear to me based on what you've said
> so far, because I'm not looking at enough of the code (and I
> don't really want to ;->)

Then take my word for it. ;) It's the same logic as for storage(p),
and you blessed that. Essentially, we've applied RAII again (and
note that it has *everything* to do with the d'tor, despite what you
said before ;). Before, the count was deleted in release(). If
the c'tor were called, but release() were never called, the count
would get leaked. By deleting the count in the d'tor, we are
guaranteed that it will always be deleted. The only time it isn't
is when we know that someone else is using it. That occurs
when --count > 0.

> [...]
> >> It'll be interesting to see how well optimizers do with
> >> smart_ptr destructors, i.e. will they be able to deduce that
> >> pointee_ has already been nulled out and eliminate the test
> >> and call to checked_delete,
> >
> > I don't see how they could.
>
> Why not?

I wonder if we're talking about the same code. ;)

> [...]
> You can work through the implications yourself:
>
> int main()
> {
> smart_ptr<int> x;
> ...
> }
>
> When x is destroyed, by looking at the definition of all these
> inline/templated functions, can _you_ deduce that ownership
> nulls the pointer, leaving nothing for storage to do?

No, I can't. But maybe that's because I'm looking at the full
interface, which includes things like x = y and reset(x, p).
Consider a more realistic case:

int main()
{
    smart_ptr<int> x;
    // 1...
    smart_ptr<int> y(x);
    // 2...
    // 3
}

At 3, does y exclusively own the resource or not? What if 2
contains this line:

reset(x, 0);

Or this line:

y = z;

> > [...]
> > Of course, order is significant
>
> That's very unclear to me. Why? If ~smart_ptr is being executed,
> all subobjects have been constructed. There's no reason to have a
> fallback destruction strategy AFAICT.

The reason order is significant is to maintain the proper ownership
sharing semantics. storage.release() will always succeed. That is,
storage is always able to release its claim to possession (meaning,
it won't clean up). If you performed that first, it would always succeed,
and never free the resource. On the other hand, ownership represents
the shared claims to ownership of all pointers managing that resource.
The only way ownership can release possession to storage (so that it
cleans up the resource) is if all other pointers have already
relinquished their claims (use_count == 1). That's why you need to
make ownership release() first, and test the result.

Dave


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