Boost logo

Boost :

From: David Abrahams (dave_at_[hidden])
Date: 2003-01-30 15:41:08


"David B. Held" <dheld_at_[hidden]> writes:

> "David Abrahams" <dave_at_[hidden]> wrote in message
> news:u1y2ujw8o.fsf_at_boost-consulting.com...
>> [...]
>> That's a very imprecise description, and exactly what I mean by
>> "not sure what you really wanted." Even though you think you have
>> an answer now, I want to encourage you to write down very carefully
>> what you think should happen in case an exception occurs at each
>> possible point. It might even tell you why your current design is
>> "obviously" the right one.
>
> All right, on the off chance that you or someone else spots a flaw in
> the logic, here goes:
>
> 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?

> storage takes ownership of p immediately, so it must also clean up
> p in the event of an exception. None of the other policies are c'ted
> yet, so we have no less than the basic guarantee, with the premise
> that smart_ptr(P) takes ownership of p immediately.
>
> 2) ownership(p) throws
>
> 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?" IIRC the cleanup of p
was supposed to be cooperative between storage and ownership. 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?

> 3) checking() throws
>
> Unlikely, but the reasoning is the same as 2). checking is
> responsible for cleaning up its own resources, but the fully c'ted
> storage will clean up p, because it "owns" it. Since ownership
> is fully c'ted, it...oops! I just realized that ownership will leak its
> count. :( This was a good exercise after all!

Yes, these "off chances" are worth investing in ;-)

> 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 ;->)

> 4) conversion() throws
>
> Even less likely, but the same reasoning as 2). conversion will
> almost certainly have no resources of its own, but the fully c'ted
> storage will clean up p, and ownership will clean up itself.
>
> 5) on_init(p) throws
>
> Since 1), 2), 3), and 4) are safe, and on_init(p) acquires no new
> resources, it inherits the same level of safety.

Good.

>> [...]
>> > But in order to solve the problem with too many c'tors for the
>> > move configuration,
>>
>> "Lots of constructors" is one of the main reasons to factor it into
>> a resource-managing base class and an interface-providing
>> derived facade.
>
> Hmm...in this case, I only have one too many c'tors. Unfortunately,
> that's one too many. :(
>
>> [...]
>> I love it when people put trademarked names on trivial things I've
>> been doing since at least '96. Well, alright, maybe I don't ;-)
>
> You just didn't have the marketing saavy to come up with a catchy
> name for it. ;>

Bingo.

But somehow "bingo" ever caught on as a name. I wonder why?

>> [...]
>> 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?

>> and will they be able to then detect that the smart_ptr is being
>> destroyed anyway and optimize away the setting of pointee_ to
>> 0?
>
> But that changes whether checked_delete actually does anything,
> no??

If its call has already been eliminated because it can never occur,
it's not a factor anymore.

>> All theoretically possible. We may need to give checked_delete
>> a throw() specification on some platforms to get there.
>
> You're gonna have to clue me in, because I don't see how a
> compiler could make any of those deductions.

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?

>> [...]
>> We'll have to see about efficiency. My approach was biased
>> towards paying in the constructor where things are probably
>> expensive anyway and leaving the destructor as simple as
>> possible.
>
> I agree that a trivial d'tor is always nice. And that particular
> c'tor is probably going to be more expensive than the other c'tors
> anyway. I just figure that zeroing a pointer is pretty cheap, and
> when the d'tor doesn't have to actually delete anything, it's just one
> pointer test, which doesn't seem like a terrible price to pay for fairly
> simple exception safety.

There are 2 issues: speed and code size. Both count and the latter
affects the former. We'll see.

>> [...]
>> This might express that notion better, though I don't remember
>> enough about the policies to say whether it's correct (also, you
>> might want to rename one of the 'release' functions, since they
>> seem to do different things):
>>
>> smart_ptr::~smart_ptr()
>> {
>> ownership_policy::release(storage_policy::release());
>> }
>> [...]
>
> Well, the way I see it, release() is relative to the policy. First,
> ownership releases a reference, which might release
> ownership's claim to the resource. If ownership can't release
> shared possession of the resource, then storage must release
> the resource to ownership.
>
> So even though storage has real physical possession of the
> resource, ownership holds the logical shared possession of the
> resource among smart pointers. While the smart pointer is live,
> this "power sharing" is viable. In the end, however, only one of
> them can determine the fate of the resource, so they each try to
> release the resource to the other.
>
> 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.

OK, I've made promises which I am not keeping, and have to go. I hope
you'll chase my assertions to their logical conclusions, since that
has proven fruitful for you so far.

Good Luck,
Dave

-- 
                       David Abrahams
   dave_at_[hidden] * http://www.boost-consulting.com
Boost support, enhancements, training, and commercial distribution

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