2

强大的异常安全保证表明,如果发生异常,操作不会改变任何程序状态。实现异常安全的复制分配的一种优雅方式是复制和交换习语

我的问题是:

  1. 对变异非原始类型的类的每个变异操作使用复制和交换会不会过大?

  2. 性能真的是强异常安全的公平交易吗?

例如:

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;
};
4

4 回答 4

3

您应该始终以基本异常保证为目标:确保在发生异常时,正确释放所有资源并且对象处于有效状态(可以未定义,但有效)。

强大的异常保证(即“事务”)是您认为有意义时应该实施的东西:您并不总是需要事务行为。

如果很容易实现事务性操作(例如通过复制和交换),那就去做吧。但有时它不是,或者它会产生很大的性能影响,即使对于赋值运算符等基本事物也是如此。我记得实现了类似 boost::variant 的东西,但我不能总是在复制分配中提供强有力的保证。

您将遇到的一个巨大困难是移动语义。移动时您确实需要事务,否则您会丢失移动的对象。但是,您不能总是提供强有力的保证:考虑std::pair<movable_nothrow, copyable>(并查看评论)。这是你必须成为noexcept大师的地方,并使用令人不舒服的元编程量。由于异常安全性,C++ 很难准确掌握。

于 2012-03-28T14:35:07.947 回答
2

与所有工程问题一样,它与平衡有关。

当然,const-ness/immutability 和强大的保证会增加对代码的信心(尤其是伴随着测试)。它们还有助于减少对错误的可能解释空间。

但是,它们可能会对性能产生影响。

像所有性能问题一样,我会说:配置文件并摆脱热点。Copy And Swap 肯定不是实现事务语义的唯一方法(它只是最简单的),所以分析会告诉你在哪里绝对不应该使用它,你必须找到替代方案。

于 2012-03-28T14:36:52.610 回答
1

这取决于您的应用程序将在什么环境中运行。如果您只是在自己的机器上运行它(频谱的一端),那么对异常安全性过于严格可能不值得。如果您正在为医疗设备(另一端)编写程序,您不希望在发生异常时留下无意的副作用。介于两者之间的任何事情都取决于对错误的容忍程度和可用于开发的资源(时间、金钱等)

于 2012-03-28T14:34:34.530 回答
0

是的,您面临的问题是这个成语很难扩展。其他答案都没有提到它,但是 Alexandrescu 发明的另一个非常有趣的成语叫做scopeGuards。它有助于提高代码的经济性,并极大地提高了需要遵守强大的异常安全保证的函数的可读性。

范围守卫的想法是一个堆栈实例,它允许将回滚函数对象附加到每个资源获取。当范围保护被破坏(通过异常)时,将调用回滚。您需要commit()在正常流程中显式调用以避免在范围退出时进行回滚调用。

检查我最近提出的这个问题,该问题与使用 c++11 功能设计安全范围保护有关。

于 2012-04-23T17:24:15.683 回答