Boost logo

Boost :

From: Fernando Cacciola (fernando_cacciola_at_[hidden])
Date: 2002-09-27 09:11:29


"David Abrahams" <dave_at_[hidden]> wrote in message
news:03e801c26625$c5e75350$6501a8c0_at_boostconsulting.com...
> From: "Fernando Cacciola (hotmail)" <fernando_cacciola_at_[hidden]>
>
>
> > Hi,
> >
> > Consider this example of "synchronized multiple updates"
>
> Good choice of problem. Most people never consider how the operations on
> sub-parts of their system affect the overall exception guarantees. I treat
> this sort of thing in
> http://www.boost.org/more/generic_exception_safety.html.
>
Thank for the link! I've read it while ago, but it is always good to get bak
to it.

> > (I'll show some details so I can focus
> > on a solution applicable to cases when both
> > sequences cannot be merged or associated intrusively.
> > Ownership issues are left out, so bare pointers are used)
> >
> >
> > class InterfacedMultiPolygon
> > {
> > MultiPolygon mpoly_ ; // sequence 1
> >
> > std::vector<PolygonInterface> interfaces_ ; // sequence 2
> >
> > void push_back ( Polygon* poly, PolygonInterface* intf )
> > {
> > // Both sequences must be synchronized.
> > mpoly_ .push_back(poly);
> > interfaces_.push_back(intf);
> > }
> > } ;
> >
> > The goal is to achieve the basic guarantee for the synchronized
> push_back.
> >
> > How would you do this?
> > (Without copying the whole thing just to move the final state at the
end,
> of
> > course)
> >
> > The only thing I could think of is to reserve storage for both
containers
> > before the push_back:
> >
> >
> >
> > void push_back ( Polygon* poly, PolygonInterface* intf )
> > {
> > // Exceptions can be thrown below...
> > mpoly_.reserve( mpoly_.size() + 1);
> > interfaces_.reserve(interfaces_.size()+1);
> >
> > // But no exceptions can be thrown after this point.
> > mpoly_ .push_back(poly);
> > interfaces_.push_back(intf);
> > }
>
>
> This only works because the copy-constructors of your element types don't
> throw.

Right.
I forgot to mention that the sort of sequences that I was considering are of
objects which themselves provide exception-safe copy construction. In
practice, this means that the sequence is of managed pointers (i.e.
shared_ptr<>) which don't throw during copy construction.

> A general solution needs a try/catch or other cleanup mechanism:
>
> {
> vector1.push_back(e1);
> try {
> vector2.push_back(e2):
> }
> catch(...) {
> vector1.pop_back();
> throw;
> }
> }
>
> And now you have not only the basic, but the strong guarantee (unless you
> consider the capacity() of vector1 to be part of the visible state of the
> composite container).
>
I see. Thanks, this will work if the element's copy-ctor could throw.

> > w.r.t to the containers (assuming that the transfers are "basically
> > exception safe"),
>
> ??? What does this mean?
>
I meant: assume that the element's copy-ctor don't throw. I'm so used to
work with such guarantees that I forgot to be explicit about it.
(I said 'transfer' instead of 'copy' because I have move-semantics in my
head :-)

> > does the technique of reserving storage always provide the basic
> guarantee?
>
> Clearly not.
>
How about in the case where copy-ctor/move don't throw.

In some applications, like mine, this is almost always the case.
If an object can throw when copied, I use handle/body and 'clone()' so that
copy/assignment (of the handle) never throws, and if I need a 'real' copy I
always write:
handle new_object = old_object->clone(); // can throw.

So I'm usually left with only contaier-reallocations to worry about.

--
Fernando Cacciola

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