Domanda

The strong exception safety guarantee says that an operation won't change any program state if an exception occurs. An elegant way of implementing exception-safe copy-assignment is the copy-and-swap idiom.

My questions are:

  1. Would it be overkill to use copy-and-swap for every mutating operation of a class that mutates non-primitive types?

  2. Is performance really a fair trade for strong exception-safety?

For example:

class A
{   
    public:
        void increment()
        {   
            // Copy
            A tmp(*this);

            // Perform throwing operations on the copy
            ++(tmp.x);
            tmp.x.crazyStuff();

            // Now that the operation is done sans exceptions, 
            // change program state
            swap(tmp);
        }   

        int setSomeProperty(int q)
        {   
            A tmp(*this);
            tmp.y.setProperty("q", q); 
            int rc = tmp.x.otherCrazyStuff();
            swap(tmp);
            return rc;
        }   

        //
        // And many others similarly
        //

        void swap(const A &a) 
        {   
            // Non-throwing swap
        }   

    private:
        SomeClass x;
        OtherClass y;
};
È stato utile?

Soluzione

As all matters of engineering, it is about balance.

Certainly, const-ness/immutability and strong guarantees increase confidence in one's code (especially accompanied with tests). They also help trim down the space of possible explanations for a bug.

However, they might have an impact on performance.

Like all performance issues, I would say: profile and get rid of the hot spots. Copy And Swap is certainly not the only way to achieve transactional semantics (it is just the easiest), so profiling will tell you where you should absolutely not use it, and you will have to find alternatives.

Altri suggerimenti

You should always aim for the basic exception guarantee: make sure that in the event of an exception, all resources are released correctly and the object is in a valid state (which can be undefined, but valid).

The strong exception guarantee (ie. "transactions") is something you should implement when you think it makes sense: you don't always need transactional behavior.

If it is easy to achieve transactional operations (eg. via copy&swap), then do it. But sometimes it is not, or it incurs a big perfomance impact, even for fundamental things like assignment operators. I remember implementing something like boost::variant where I could not always provide the strong guarantee in the copy assignment.

One tremendous difficulty you'll encounter is with move semantics. You do want transactions when moving, because otherwise you lose the moved object. However, you cannot always provide the strong guarantee: think about std::pair<movable_nothrow, copyable> (and see the comments). This is where you have to become a noexcept virtuoso, and use an uncomfortable amount of metaprogramming. C++ is difficult to master precisely because of exception safety.

It depends on what environment your application is going to run in. If you just run it on your own machine (one end of the spectrum), it might not be worth to be too strict on exception safety. If you are writing a program e.g. for medical devices (the other end), you do not want unintentional side-effects left behind when an exception occurs. Anything in-between is dependent on the level of tolerance for errors and available resources for development (time, money, etc.)

Yes, the problem you are facing is that this idiom is very hard to scale. None of the other answers mentioned it, but another very interesting idiom invented by Alexandrescu called scopeGuards. It helps to enhance the economy of the code and makes huge improvements in readability of functions that need to conform to strong exception safety guarantees.

The idea of a scope guard is a stack instance that lets just attach rollback function objects to each resource adquisition. when the scope guard is destructed (by an exception) the rollback is invoked. You need to explicitly call commit() in normal flow to avoid the rollback invocation at scope exit.

Check this recent question from me that is related to designed a safe scopeguard using c++11 features.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top