Boost logo

Boost :

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


"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. Even if only one (sub)object is being
initialized with a resource, you may still require other possibly throwing
members in your object.

> > You could say that no class should acquire a resource and have
> > other members/bases that could throw
>
> I don't think I'd say that.

Well at least we agree on something.

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

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

?

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

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

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

> [...]
> So, does that help with your problem?

Hmm...I don't like having to create an impl base, but if I have to
specialize for move semantics anyway, I might be able to kill two
birds with one stone. 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!"

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

> > Another syntax might be to pass the exception type to the
> > destructor. Then, a given destructor would only get called if its
> > argument were convertible to the thrown exception type, like so:
> >
> > scalar_storage::~scalar_storage(std::bad_alloc const&)
> > {
> > // failed allocation somewhere else, clean up
> > destroy();
> > }
> >
> > scalar_storage::~scalar_storage(std::exception const&)
> > {
> > // some other problem, clean up anyway ;)
> > destroy();
> > }
>
> 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.

> > [...]
> > ref_counted::~ref_counted(int) would get called.
>
> And if that weren't defined?
> [Earlier...]
> > In the event that no specialized d'tor were defined, the normal
> > destructor would get called.

> [...]
> How about the default argument?

I prefer to call it "The Executioner Idiom". ;>

Dave

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

template <class P>
class cleaner_
{
    cleaner() : p_(P::default_value()) { }

    void contract(typename P::pointer_type p) { p_ = p; }
    void hit()
    {
        if (p_ != P::default_value() P::destroy(p);
        p_ = P::default_value();
    }
    void cancel() { p_ = P::default_value(); }
};

template <class P>
cleaner_& cleaner()
{
    static cleaner_<P> c;
    return c;
}

template <class P>
struct boss
{
    boss(typename P::pointer_type p) { cleaner<P>().contract(p); }
    ~boss() { cleaner<P>().hit(); }
    void cancel() { cleaner<P>().cancel(); }
};

class smart_ptr : ...
{
    boss b;
    ...
public:
    smart_ptr(P p) : boss<storage>(p), storage(p), ownership(p)
    { boss.cancel(); }
    ...
};


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