Thread: Exception-Safe Copy Assignment

  1. #1
    Registered User
    Join Date
    May 2006
    Posts
    1,579

    Exception-Safe Copy Assignment

    Hello everyone,


    http://www.gotw.ca/gotw/059.htm

    The reason why we can not write an exception safe copy assignment for class Widget, is because (suppose we assign t1_, then assign t2_) if T1::operator=() does not throw exception, and T2::operator=() throws exception, then since all operations on T1 may throw, it means we can not find any operations on T1 which could rollback the original status to T1 without exception thrown (if exception is thrown during rollback, means rollback is not successful, and T1 will be in an in-consistent status which is different from original status -- which violates the strong exception safe guideline).

    My understanding correct?

    I quote the related paragraph below as well.

    --------------------
    3. Consider the following class:

    Code:
        //  Example 3: The Cargill Widget Example
        //
        class Widget
        {
          // ...
        private:
          T1 t1_;
          T2 t2_;
        };
    Assume that any T1 or T2 operation might throw. Without changing the structure of the class, is it possible to write a strongly exception-safe Widget::operator=( const Widget& )? Why or why not? Draw conclusions.

    Short answer: In general, no, it can't be done without changing the structure of Widget.

    In the Example 3 case, it's not possible to write a strongly exception-safe Widget::operator=() because there's no way that we can change the state of both of the t1_ and t2_ members atomically. Say that we attempt to change t1_, then attempt to change t2_. The problem is twofold:

    1. If the attempt to change t1_ throws, t1_ must be unchanged. That is, to make Widget::operator=() strongly exception-safe relies fundamentally on the exception safety guarantees provided by T1, namely that T1::operator=() (or whatever mutating function we are using) either succeeds or does not change its target. This comes close to requiring the strong guarantee of T1::operator=(). (The same reasoning applies to T2::operator=().)

    2. If the attempt to change t1_ succeeds, but the attempt to change t2_ throws, we've entered a "halfway" state and cannot in general roll back the change already made to t1_.
    --------------------


    thanks in advance,
    George
    Last edited by Salem; 03-27-2008 at 12:04 PM. Reason: Turn off smilies for the C++ code in the prose

  2. #2
    Registered User
    Join Date
    Jun 2005
    Posts
    6,815
    Yes and no.

    You are correct if it is assumed that the structure of "Widget" is not allowed to change, as there is no way to make operations on T1 or T2 atomic.

    However, if types T1 and T2 both provide the basic Abraham's guarantee or better, is is possible to implement Widget so it provides the strong guarantee, but that requires changing the structure of Widget (so it contains an auto_ptr<T1> and an auto_ptr<T2>). It is not possible to any useful guarantee without that structural change of Widget.

    If either type T1 or type T2 fails to provide even the basic guarantee, then all bets are off, and it is not possible to implement Widget in an exception-safe manner.

  3. #3
    Officially An Architect brewbuck's Avatar
    Join Date
    Mar 2007
    Location
    Portland, OR
    Posts
    7,396
    It seems doable if T1 and T2 have exception-free swap() methods. Then you could do this:

    Code:
    widget &widget::operator=(const widget &widg)
    {
        try
        {
            // An exception could get thrown in these next two assignments
            T1 t1 = widg._t1;
            T2 t2 = widg._t2;
            // But not here, since swap() is no-throw
            _t1.swap(t1);
            _t2.swap(t2);
        }
        catch(...) { throw; }
        return *this;
    }
    Last edited by brewbuck; 03-27-2008 at 12:34 PM.

  4. #4
    Registered User
    Join Date
    May 2006
    Posts
    1,579
    Hi grumpy,


    Abraham's exception theory are 3 leves, basic/strong/nothrow, in your bet below, which level do you assume?

    Quote Originally Posted by grumpy View Post
    Yes and no.

    You are correct if it is assumed that the structure of "Widget" is not allowed to change, as there is no way to make operations on T1 or T2 atomic.

    However, if types T1 and T2 both provide the basic Abraham's guarantee or better, is is possible to implement Widget so it provides the strong guarantee, but that requires changing the structure of Widget (so it contains an auto_ptr<T1> and an auto_ptr<T2>). It is not possible to any useful guarantee without that structural change of Widget.

    If either type T1 or type T2 fails to provide even the basic guarantee, then all bets are off, and it is not possible to implement Widget in an exception-safe manner.

    Thanks brewbuck,


    Quote Originally Posted by brewbuck View Post
    It seems doable if T1 and T2 have exception-free swap() methods. Then you could do this:

    Code:
    widget &widget::operator=(const widget &widg)
    {
        try
        {
            // An exception could get thrown in these next two assignments
            T1 t1 = widg._t1;
            T2 t2 = widg._t2;
            // But not here, since swap() is no-throw
            _t1.swap(t1);
            _t2.swap(t2);
        }
        catch(...) { throw; }
        return *this;
    }
    Clear reply and code.


    regards,
    George

  5. #5
    Algorithm Dissector iMalc's Avatar
    Join Date
    Dec 2005
    Location
    New Zealand
    Posts
    6,318
    This is for illustration purposes I assume?
    Quote Originally Posted by brewbuck View Post
    Code:
        catch(...) { throw; }
    The try block can of course be eliminated in this case.

    I've started getting in the habbit of providing non-throwing swap functions for my classes as well.
    My homepage
    Advice: Take only as directed - If symptoms persist, please see your debugger

    Linus Torvalds: "But it clearly is the only right way. The fact that everybody else does it some other way only means that they are wrong"

  6. #6
    Registered User
    Join Date
    May 2006
    Posts
    1,579
    Hi iMalc,


    What do you think is wrong with brewbuck' code?

    I think the code below is correct, and it will catch any exception and rethrow the original exception. Any comments?

    Code:
    catch (...)
    {
        throw;
    }
    Quote Originally Posted by iMalc View Post
    This is for illustration purposes I assume?
    The try block can of course be eliminated in this case.

    I've started getting in the habbit of providing non-throwing swap functions for my classes as well.

    regards,
    George

  7. #7
    Registered User
    Join Date
    Jun 2005
    Posts
    6,815
    Quote Originally Posted by George2 View Post
    Abraham's exception theory are 3 leves, basic/strong/nothrow, in your bet below, which level do you assume?
    I made no assumption. I meant that, if either T1 or T2 provides no guarantee (which, in practice, can make it impossible to implement a non-throwing swap()), then it is not possible to provide any guarantee in class Widget.

    Also, actually, in Abrahams theory there are four levels: the other is "no guarantee".

    The basic guarantee is the minimum necessary to be useful in practice (unless resource leaks are deemed acceptable). The strong guarantee implies the basic guarantee. The nothrow guarantee means that exceptions will not be thrown.

  8. #8
    Registered User
    Join Date
    May 2006
    Posts
    1,579
    Thanks grumpy,


    Let me summarize and confirm finally for this thread that, the root cause of why there is no stong exception safety assignment operator is neither T1 nor T2 could provide its own nothrow version public interface (e.g. swap), which guarantees to rollback to the original status during exception. Right?

    Quote Originally Posted by grumpy View Post
    I made no assumption. I meant that, if either T1 or T2 provides no guarantee (which, in practice, can make it impossible to implement a non-throwing swap()), then it is not possible to provide any guarantee in class Widget.

    Also, actually, in Abrahams theory there are four levels: the other is "no guarantee".

    The basic guarantee is the minimum necessary to be useful in practice (unless resource leaks are deemed acceptable). The strong guarantee implies the basic guarantee. The nothrow guarantee means that exceptions will not be thrown.

    regards,
    George

  9. #9
    The larch
    Join Date
    May 2006
    Posts
    3,573
    Quote Originally Posted by George2 View Post
    Hi iMalc,


    What do you think is wrong with brewbuck' code?

    I think the code below is correct, and it will catch any exception and rethrow the original exception. Any comments?

    Code:
    catch (...)
    {
        throw;
    }
    Wouldn't the exception propagate to the caller just the same without the try/catch block?
    I might be wrong.

    Thank you, anon. You sure know how to recognize different types of trees from quite a long way away.
    Quoted more than 1000 times (I hope).

  10. #10
    Registered User
    Join Date
    May 2006
    Posts
    1,579
    Hi anon,


    I think we can do something inside try block, e.g. writing a log.

    BTW, why do we discuss try/catch block, how do you think it relates to my original question?

    Quote Originally Posted by anon View Post
    Wouldn't the exception propagate to the caller just the same without the try/catch block?

    regards,
    George

  11. #11
    Registered User
    Join Date
    Jun 2005
    Posts
    6,815
    Quote Originally Posted by George2 View Post
    Let me summarize and confirm finally for this thread that, the root cause of why there is no stong exception safety assignment operator is neither T1 nor T2 could provide its own nothrow version public interface (e.g. swap), which guarantees to rollback to the original status during exception. Right?
    It's actually the reverse. If it is not possible to implement operations on T1 or T2 that provide required guarantees, then it is not possible for your "Widget" to provide guarantees if it uses those operations. That's just as true for whether the operation is a copy constructor or a swap() operation.

    The lack of a non-throwing swap is not (specifically) a sign that a class cannot be used in a manner consistent with exception safety; it's lack could simply be an oversight rather than a sign that it could not be implemented. However, a non-throwing swap() operation is often provided as a means of achieving required exception safety for other operations.

  12. #12
    Officially An Architect brewbuck's Avatar
    Join Date
    Mar 2007
    Location
    Portland, OR
    Posts
    7,396
    Quote Originally Posted by iMalc View Post
    This is for illustration purposes I assume?
    The try block can of course be eliminated in this case.
    Yes, I was just trying to make it clear that an exception might originate from that block of code. It isn't needed.

  13. #13
    Registered User
    Join Date
    May 2006
    Posts
    1,579
    Thanks grumpy,


    I think you mean the root cause is no exception safe swap, so we can not write exception safe assignment operator, right?

    Quote Originally Posted by grumpy View Post
    It's actually the reverse. If it is not possible to implement operations on T1 or T2 that provide required guarantees, then it is not possible for your "Widget" to provide guarantees if it uses those operations. That's just as true for whether the operation is a copy constructor or a swap() operation.

    The lack of a non-throwing swap is not (specifically) a sign that a class cannot be used in a manner consistent with exception safety; it's lack could simply be an oversight rather than a sign that it could not be implemented. However, a non-throwing swap() operation is often provided as a means of achieving required exception safety for other operations.

    regards,
    George

  14. #14
    Registered User
    Join Date
    Jun 2005
    Posts
    6,815
    Quote Originally Posted by George2 View Post
    I think you mean the root cause is no exception safe swap, so we can not write exception safe assignment operator, right?
    No. There are three possible root causes of types T1 and T2 not offering exception safety guarantees.

    1) They are designed or implemented without specific consideration of exception safety, and unable to be modified to achieve that. (i.e. a common practical scenario, particularly if the class is only available as a header file and a library without source code).

    2) It is technically impossible to implement exception safety guarantees for the class (due to the nature of it's behaviour, reliance on other software, reliance on hardware, etc etc).

    3) A deliberate design trade-off, as providing desired guarantees is too costly (eg excessive performance penalties).

    Non-existence of an exception-safe swap operation or of a exception-safe assignment operator (or a copy constructor) are consequences of one (or more) of the above, not causes.

    Also, a non-throwing swap() operation is not a specific requirement to implement a exception-safe assignment operator. Using a copy constructor (which offers suitable guarantees) and a non-throwing swap() is a common and elegant way to implement an assignment operator with required guarantees, but there are other (less elegant) ways to do it.

  15. #15
    Registered User
    Join Date
    May 2006
    Posts
    1,579
    Thanks grumpy,


    Quote Originally Posted by grumpy View Post
    Also, a non-throwing swap() operation is not a specific requirement to implement a exception-safe assignment operator. Using a copy constructor (which offers suitable guarantees) and a non-throwing swap() is a common and elegant way to implement an assignment operator with required guarantees, but there are other (less elegant) ways to do it.
    Can you show me other ways to do assignment operator without using nothrow swap and also making assignment operator exception safe please?


    regards,
    George

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. intialize array allocated by new
    By lehe in forum C++ Programming
    Replies: 20
    Last Post: 06-10-2009, 09:38 PM
  2. Gcc can't find obvious copy constructor
    By SevenThunders in forum C++ Programming
    Replies: 13
    Last Post: 03-19-2009, 02:41 PM
  3. using swap to make assignment operator exception safe
    By George2 in forum C++ Programming
    Replies: 9
    Last Post: 01-10-2008, 06:32 AM
  4. is such exception handling approach good?
    By George2 in forum C++ Programming
    Replies: 8
    Last Post: 12-27-2007, 08:54 AM
  5. Copy Constructor Help
    By Jubba in forum C++ Programming
    Replies: 2
    Last Post: 11-07-2001, 11:15 AM