如何设计具有强异常保证的函数?

问题描述

我有一个函数,我希望它具有强大的异常保证:

onerror=this.style.display='none';

我能想到的具有强异常保证的唯一方法如下:

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

然而,这真的很丑陋且容易出错!!有没有比我上面提到的更好的解决方案?我可以听到有人告诉我需要使用 RAII,但我不知道如何使用,因为 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; } } }; 操作必须在函数正常返回时完成。

我也希望任何解决方案都是零——快乐路径上的开销;我真的需要尽可能快的快乐之路。

解决方法

解决方案是使用 scope guards

参见 this answer 以获得它们的示例实现;我不会在这里重复。使用范围保护,您的代码将如下所示:

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(); )

这里,FINALLY_ON_THROW 是一个宏(见上面的链接)。它不是立即执行它的参数,而是在您由于异常离开当前范围时执行它。如果您以正常方式离开范围,则该参数将被忽略。如果您在控制权首先到达守卫之前离开范围,它也会被忽略。

请注意,如果最后一个守卫之后(在同一范围内)没有任何东西可以抛出,则它是多余的。

,

简单地弹出一个问题是它不一定会将向量恢复到原始状态。如果添加元素导致任何向量重新分配,则对元素的迭代器/引用将失效,并且该失效无法回滚,从而无法保证强异常。

一个安全、简单和通用的解决方案是对副本进行修改。复制当然有额外的费用。

void some_function() {
    auto copy = *this;
    copy.vector1.push_back(/*...*/); // May Throw
    copy.vector2.push_back(/*...*/); // May Throw
    copy.vector3.push_back(/*...*/); // May Throw
    copy.vector4.push_back(/*...*/); // May Throw
    *this = std::move(copy);
}

HolyBlackCat's 范围保护建议是在可能回滚的情况下的优雅解决方案,例如,如果您使用另一个不会使迭代器/引用无效的容器,或者您根本不关心无效,或者您有一个类不变量,可以防止在容量已满时调用该函数。

您可以可行地,以边际额外成本,首先检查所有向量是否具有额外容量,然后根据检查在复制和回滚之间进行选择。如果调用者事先预留了足够的容量,这允许调用者不支付复制成本。然而,这确实偏离了优雅。

,

您可以通过多种方式实现...例如:

#include <vector>
#include <type_traits>
#include <exception>


template<class F>
struct on_fail
{
    F   f_;
    int count_{ std::uncaught_exceptions() };

    ~on_fail()
    {
        // C++20 is here and still no easy way to tell "unwinding" and "leaving scope" apart
        if (std::uncaught_exceptions() > count_) f_();
    }
};

template<class F> on_fail(F) -> on_fail<F>;


auto emplace_back_x(auto& v,auto&& x)
{
    v.emplace_back(std::forward<decltype(x)>(x));
    return on_fail{[&v]{ v.pop_back(); }};
}


int bar();


template<class F>
struct inplacer
{
    F f_;
    operator std::invoke_result_t<F&>() { return f_(); }
};

template<class F> inplacer(F) -> inplacer<F>;


void foo()
{
    std::vector<int> v1,v2,v3;
    auto rollback1 = emplace_back_x(v1,1);
    auto rollback2 = emplace_back_x(v2,inplacer{ bar });
    auto rollback3 = emplace_back_x(v3,inplacer{ []{ return bar() + 1; } });
}

请注意,您的示例不正确:如果 push_back()std::bad_alloc(或任何其他异常)失败 - 您无法执行撤消步骤。

另外,也许在您的情况下使用基本保证有意义?在实践中,你经常可以在更高的层次上处理它——例如断开连接并丢弃整个累积状态,让客户端重新连接并重复尝试。

,

这个怎么样?

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

但是……你真的需要 4 个不同的向量吗?

class X {
   /* Fields and stuff */
   void some_function() {
       vector1234.push_back(std::make_tuple(/*...*/,/*...*/,/*...*/)); // May Throw
   }
};

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...