Boost logo

Boost :

From: David Abrahams (dave_at_[hidden])
Date: 2003-01-30 04:59:48


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

> "David Abrahams" <dave_at_[hidden]> wrote in message
> news:uadhj9vne.fsf_at_boost-consulting.com...
>> [...]
>> Which is why the first "I" in RAII stands for "is". Each acquired
>> resource should initialize exactly one (sub)object.
>>
>> > Or acquiring a resource in any other context when members can
>> > throw will have the same problem.
>>
>> I can't picture what you mean.
>
> foo::foo(resource const& r)
> : r_(r), my_data_member_whose_ctor_can_throw()
> { }
>
> Here, r_ can't tell if foo is being destructed, or just r_, and that
> could be a very important difference.

It usually isn't. I'm tempted to assert that it shouldn't be.
When does that distinction ever matter?

> Even if only one (sub)object is being initialized with a resource,

I don't think I ever suggested that should be the case. However, one
(sub)object should have responsibility for managing the resource at
any given time.

> you may still require other possibly throwing members in your
> object.

I don't see a problem with that.

>> > but that seems draconian. So what is the real solution?
>>
>> To this particular problem? I'd say "redesign or respecify". That
>> is, move the design or move the goalposts.
>
> I'd rather change the rulebook. ;)

That's one approach which guarantees no practical solution in the next
few years ;-)

>> [...]
>> It's very easy to blame the language for not allowing us to easily
>> implement DWIM semantics,
>

DWIM == "Do What I Mean." My sense was that you weren't yet sure what
you really wanted, and that if you wrote it down carefully at least
one of the two following things would become apparent:

   1. How to do it in the current language

   2. That if you changed C++ to make it adjust for this case it would
      undermine important guarantees the language currently provides.

Please excuse the brashness of this interpretation; it's just my
intuitive sense.

>> especially when we're not sure exactly what those semantics should
>> be. Have you got a clear idea of what should happen during
>> construction if an exception is thrown? Can you write it as
>> pseudocode?
>
> I think I did later on. Allow users to define an alternative
> destructor for the case of member destruction during a constructor
> call.

No, no, that's not what I meant! I meant, "do you know what behavior
you want from your smart_ptr design?" Have you specified it
carefully?

>> The language gives us a very coherent mechanism for dividing up
>> the units of work that must get done when a constructor throws an
>> exception. See page 53 of "more effective C++"
>> [...] Any "fix" to the language will only be an improvement if it doesn't
>> undermine that coherence.
>
> That mechanism only works with a trivial acquire/release pattern. In
> particular, the pattern when you acquire in the c'tor and release in the
> d'tor.

You can always apply that pattern. You just need to slice and dice
your members and bases in such a way as to make it possible.

> While that works quite well for a great number of uses, it seems
> unlikely to me that smart pointers are the only situation in which you
> need to do cleanup someplace besides the d'tor.

I think you're getting trapped by those last 2 words: "the d'tor".
You can always arrange for cleanup to happen in _a_ d'tor. Indeed
some people make a justifiable claim that cleanup should never happen
in catch blocks in portable code. So many real systems wind
"asynchronous" non-C++ exceptions into the C++ EH mechanism that
catch(...) is often not safe in practice.

> And when that cleanup cannot get activated because of an exceptional
> path, we need some assistance from the compiler to tell us that the
> exception occurred.

I still don't buy this, but I'm willing to be convinced.

>> [...]
>> One language change I could imagine which would help would be
>> the ability to declare an auto variable in the class' initializer list:
>>
>> template<class P, class D> shared_count(P p, D d)
>> : auto deleter<P,D> del(d, p)
>> , pi_(new sp_counted_base_impl<P, D>(p, d))
>> {
>> del.release();
>> }
>
> Like I said, this case is already covered by function try blocks, so I
> don't think it needs to be resolved.

I agree that the case above is probably equivalent to something you
can do with a function-try-block, however I envisioned being able to
re-use the variable during initialization, or declare it somewhere in
the middle of base or member initialization. That's a bit different.

But anyway, I only got started down this road of considering language
extensions as a way to find a solution in the current language, which
I hope it led me to...

>> [...]
>> So, does that help with your problem?
>
> Hmm...I don't like having to create an impl base

I think you're now referring to my reply to Andrei which you don't
cite here (the one with pb_impl)?

> but if I have to specialize for move semantics anyway, I might be
> able to kill two birds with one stone.

Don't know what that's about, but I'm not sure I need to.

> It's interesting that your trick uses a kind of negative logic. It
> holds the axe over the neck of the resource until the c'tor body
> yells: "Don't do it!"

I don't think this is a new idea. It's a simple for of
transfer-of-ownership.

>
>> [...]
>> > One syntax would be to pass a dummy int parameter to a normal
>> > destructor, like so:
>> >
>> > scalar_storage::scalar_storage()
>> > {
>> > // normal destruction, do nothing
>> > }
>> >
>> > scalar_storage::~scalar_storage(int)
>> > {
>> > // something bad happened, clean up with extreme prejudice
>> > destroy();
>> > }
>>
>> That would mean the usual destructor wouldn't be called in case of an
>> exception?
>
> Correct.

I think this breaks what is a very clean model, and I'm tempted to
assert that classes which would take advantage of this are badly
designed. Why should scalar_storage be destroyed differently based on
whether some arbitrary sibling member or derived class constructor
throws an exception?

<snip>
>> Yikes! Very complicated! Probably misguided, as most things which
>> distinguish different exception types near the throw point tend to be.
>
> I'm not really interested in distinguishing the exception types as much as
> being able to call a different d'tor for exceptional cases. It just seems
> that we might want to be able to call more than one type of d'tor, and
> that seemed the most logical way to classify them.

But all still cart-before-horse IMO. It still looks like you're
redesigning the language without deeply understanding the problem
you're trying to solve by doing so. I could be wrong about that, but
it would sure help to see a precise description of what you want to
happen in smart_ptr.

> P.S. This isn't thread safe, but maybe this is a direction to think in:

<snip>

You clearly have policies-on-the-brain <wink>

I can't tell which part of what you're doing here you think is worthy
of note, but it seems like it has more problems than just thread
safety, and I don't see it solving many problems... I could be wrong
though. It wouldn't be the first time ;-)

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