Boost logo

Boost :

From: David Abrahams (dave_at_[hidden])
Date: 2003-01-29 16:24:09


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

> "David Abrahams" <dave_at_[hidden]> wrote in message
> news:ufzrblnvz.fsf_at_boost-consulting.com...
>> > [...]
>> > Unfortunately, StoragePolicy doesn't know when other c'tors have
>> > failed. The only one who does is smart_ptr, which is why it seems
>> > I have to either A) use a function try block to inform StoragePolicy
>> > that someone else has failed, and clean up appropriately,
>>
>> Why not just arrange for StoragePolicy to be the first constructed?
>
> smart_ptr(T* p)
> : storage(p), ownership(p), conversion(), checking()
> { ... }
>
> Suppose storage(p) succeeds, but ownership(p) throws (such as while
> allocating a count).

Ah, I had forgotten the details of the policy decomposition.

> You can't rely on ~storage() to do cleanup, because
> storage doesn't know what the ownership strategy is. It only knows
> about storage. Here's what ~smart_ptr() looks like:
>
> ~smart_ptr()
> {
> if (ownership_policy::release(get_impl(*this)))
> {
> storage_policy::destroy();
> }
> }
>
> So you see, storage_policy must be told when to do cleanup by
> smart_ptr (that's the principle of orthogonal policies).

Then you've indeed got a problem. There were indications in some of
Beman's earlier explorations that the orthogonal policy decomposition
wasn't always a natural one. This might be another clue.

> It's easy enough to write storage_policy::storage_policy(T* p) to
> clean up after itself if *it* throws. But getting it to clean up
> after *another* policy is the trick. Obviously, if one of the other
> c'tors throws, ~smart_ptr() never gets called, so how do we tell
> storage_policy that a failure occurred, and it needs to clean up?
> The only way I can see is to use a function try block:
>
> smart_ptr(T* p)
> try
> : storage_policy(p), ownership(p), conversion(), checking()
> { ... }
> catch
> {
> storage_policy::destroy();

This looks like a static member function call. How does it get ahold
of p?

IIRC one of the reasons function-try-blocks are often useless is that
the data members are already gone, so you can't touch them.

> }
>
> Unfortunately, that's totally Greek to bcc 5.5 (and probably vc++,
> though I don't have access to it right now), not to mention that it
> requires storage_policy(p) to be no-throw.

And conversion and checking, no? If one of those fails, don't you
want the regular release sequence you cite above to take effect?

This appears to be the lesson that Cargill failed to learn from his
own article all over again: some designs are incompatible with certain
EH properties (just like some are incompatible with certain efficiency
properties, etc.).

>> > or B) not use initializer lists, and do all the work in the c'tor
>> > body of smart_ptr.
>>
>> I don't see how B can work; the resource will be unmanaged at
>> least until the body is entered, while all the bases and members
>> are constructed.
>
> That can only work if the default c'tors are no-throw, which would
> mean special-casing ref_counted to not allocate a count until it
> actually receives a pointer. But it would also make the rest of the
> functions in ref_counted bulkier because now you have to detect a
> null count pointer. Otherwise, you would have to set the count
> pointer to null, and have the body of smart_ptr ask ref_counted if
> he was built properly. Quite messy. I don't see any nice solutions
> to this problem.

Me neither, other than changing the design.

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