c++raiiexception-safety

How do I design a function with a strong exception guarantee?


I have a function which I would like to have the strong exception guarantee:

class X {
   /* Fields and stuff */
   void some_function() {
       vector1.push_back(/*...*/); // May Throw
       vector2.push_back(/*...*/); // May Throw
       vector3.push_back(/*...*/); // May Throw
       vector4.push_back(/*...*/); // May Throw
   }
};

The only way I can think of making this having the strong exception guarantee is the following:

class X {
   /* Fields and stuff */
   void some_function() {
       try { vector1.push_back(/*...*/);};
       catch (Vector1PushBackException) {
            throw Vector1PushBackException;
       }
       try { vector2.push_back(/*...*/);};
       catch (Vector2PushBackException) {
            vector1.pop_back();
            throw Vector2PushBackException;
       }
       try { vector3.push_back(/*...*/);};
       catch (Vector3PushBackException) {
            vector1.pop_back();
            vector2.pop_back();
            throw Vector3PushBackException;
       }
       try { vector4.push_back(/*...*/);};
       catch (Vector4PushBackException) {
            vector1.pop_back();
            vector2.pop_back();
            vector3.pop_back();
            throw Vector4PushBackException;
       }
   }
};


However, this is really ugly and error-prone!! Is there a better solution than the one I put above? I can hear someone telling me that I need to use RAII, but I can't figure it out how, since the pop_back operations must not be done when the function returns normally.

I would also like any solution to be zero - overhead on the happy path; I really need the happy path to be as fast as possible.


Solution

  • The solution is to use scope guards.

    See this answer for an example implementation of them; I'm not going to repeat it here. With scope guards, your code will look like this:

    vector1.push_back(/*...*/);
    FINALLY_ON_THROW( vector1.pop_back(); )
    vector2.push_back(/*...*/);
    FINALLY_ON_THROW( vector2.pop_back(); )
    vector3.push_back(/*...*/);
    FINALLY_ON_THROW( vector3.pop_back(); )
    vector4.push_back(/*...*/);
    FINALLY_ON_THROW( vector4.pop_back(); )
    

    Here, FINALLY_ON_THROW is a macro (see link above). Rather than executing it's parameter immediately, it causes it to be executed when you leave the current scope because of an exception. If you instead leave the scope the normal way, the parameter is ignored. If you leave the scope before control reaches the guard in the first place, it's also ignored.

    Note that the last guard is superfluous if nothing after it (in the same scope) can throw.