Boost logo

Boost :

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


"David Abrahams" <dave_at_[hidden]> wrote in message
news:uptqfrm97.fsf_at_boost-consulting.com...
> > [...]
> > 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?

Well, if r_ is being destroyed because of an exception, and it owns a
resource, it needs to clean it up, because it is the only one that knows
about the resource. If r_ is being destroyed normally, and under
normal circumstances, cleans up its resources before the d'tor is
called (nor should clean them up in the d'tor for the normal case),
then the d'tor *shouldn't* clean up resources (because it could be
doing so prematurely). I assume this happens any time a type has
ownership transfer (move) semantics.

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

But if that (sub)object is nested within another object, then you are
going to run into the same exception issues we have here, even if you
are trying to practice "True RAII".

> [...]
> DWIM == "Do What I Mean." My sense was that you weren't yet
> sure what you really wanted,

Well, it seems obvious what I wanted: if any policy c'tor throws, a
magical routine would descend from heaven and clean up the mess.

> and that if you wrote it down carefully at least one of the two
> following things would become apparent:

Well, I think we all wrote it in several ways, few of which turned out
to be very useful.

> 1. How to do it in the current language

Believe it or not, I think I have a solution. ;)

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

I don't think my exceptional d'tors would undermine important language
guarantees, but I'm not sure they're really necessary now, either.

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

I didn't believe this when I first read it, but in trying to come up with
a counter-example, I think I came up with a nifty solution.

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

Yes. Sorry.

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

Well, your solution requires me to create a wrapper on top of the
current smart_ptr class, which is a major PITA. But in order to solve
the problem with too many c'tors for the move configuration, it looks
like I'll have to create a partial specialization for move and non-move
versions, which of course, calls for a common base class anyway.
So if I'm going to do the work of creating another layer of indirection,
I might as well get two things from it.

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

Yup. It's a member-initializer version of ScopeGuard(TM).

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

Namely, because it takes ownership of a resource which will be
transferred away, shared, or cleaned up before its d'tor is called in
the normal case, but which must be cleaned up immediately in the
exceptional case.

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

Basically, I was trying to avoid having to create another layer of
indirection (which is a lot of work!). Here's my new solution:
Integrate the deleter with the subobject that takes ownership,
or: *arrange for cleanup to happen in the d'tor*.

storage_policy(stored_type const& p) : pointee_(p)
{ }

~storage_policy::storage_policy()
{
    boost::checked_delete(pointee_);
}

void storage_policy::release()
{
    pointee_ = 0;
}

smart_ptr::smart_ptr(P p)
    : storage_policy(p), ownership(p), checking(), conversion()
{
}

smart_ptr::~smart_ptr()
{
    if (!ownership_policy::release(get_impl(*this)))
    {
        storage_policy::release();
    }
}

Now, storage_policy(P) must simply clean up p if it wants to throw.
No problem there (current impl doesn't throw anyway). All we did
was reverse the logic so that storage_policy always attempts to clean
up, and we tell it *not* to if it shouldn't. RAII to the rescue! Now
storage_policy is behaving like a well-mannered auto_ptr<> child.
No base impl necessary, no unnatural no-throw guarantees required,
no goofy helper classes involved, and it's as exception-safe as I
know how to make it.

Dave, I hereby dub thee "Prophet of RAII". ;> And Andrei is now
"Prophet of Orthogonality". By not straying from these guiding
principles, the optimal solution has been found! Hallelujah!

Dave

P.S. What you said earlier now makes perfect sense. The problem
was that both storage and ownership were trying to own the resource
cooperatively. That led to the peculiar situation that storage acquired
the resource, and ownership controlled its destruction, which makes
for difficult exception safety. Now, we let storage own the resource,
and we relinquish control to ownership at the 11th hour, if ownership
tells us that it's really still a shared resource. As far as storage is
concerned, it's transferring its possession to ownership. One
resource controlled by one (sub)object. I think I finally get it. ;)


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