如果在堆栈展开期间异常*不*留下析构函数,会发生什么?

问题描述 投票:0回答:1

我正在尝试编写一个合同设计库。 我的一种类型定义为:

#include <stdexcept>
#include <type_traits>
#include <utility>

template <typename T, typename Post>
class out {
  public:
    out(T& param, Post check = {})
      : param{param},
        copy{param}, //must be copyable; may throw
        check{std::move(check)}
    {}

    ~out() noexcept(false) {
        if (!check(param)) {
            param = std::move(copy); //rollback; assume this never throws
            if (!std::uncaught_exceptions()) //no exception currently being thrown
                throw std::invalid_argument("postcondition violated"); // leaves destructor if another exception is not being thrown
        }
    }

  private:
    T& param;
    T copy;
    Post check;
};

以下是如何使用它的示例:

#include <vector>
#include <cassert>

struct is_not_empty {
    bool operator()(const std::vector<int>& v) { return !v.empty(); }

    /*not real code, just meant to illustrate the problem*/
    is_not_empty() { /*acquire some resource*/ }
    ~is_not_empty() { /*release some resource*/ }
    /******/
};

void function_with_postcondition(out<std::vector<int>, is_not_empty> v) {
} // Exception thrown here in out<>::~out

int main() {
    std::vector<int> v; //empty
    try {
        function_with_postcondition(v);
        assert(false); //not reached
    }
    catch (const std::invalid_argument& e) {
    }
}

我的问题 -

v
对象是否已在
function_with_postcondition()
中正确销毁?
out<>::Post
成员是否被正确销毁? 在这个例子中是否存在资源泄漏?我是否递归调用析构函数? 这是明确定义的吗? 我真正想要了解的是确切发生的操作的(明确定义/未定义)顺序。

我知道堆栈展开,并且我知道在已经抛出异常时抛出异常会导致对

std::terminate()
的调用 - 这就是我在析构函数中调用
std::uncaught_exceptions()
时试图避免的情况。 如果函数抛出异常,我不想检查后置条件,因为函数异常退出。

最后,为什么不采用另一种方式(例如

assert()
abort()
exit()
等)? 我正在尝试编写一个库来检查在异常环境中稳健的函数前置条件和后置条件。 我正在尝试减少所需的样板数量,同时允许允许重试操作的提交/回滚语义。

c++ exception destructor stack-unwinding
1个回答
1
投票

引用[除了.ctor]

3 如果对象的析构函数因异常而终止,则执行在执行析构函数主体([class.dtor])之后执行且尚未开始执行的每个析构函数调用。

因此我们保证在展开堆栈之前至少会尝试调用成员和基类的析构函数。只要相关的其他对象表现得相当良好,这一切都很好。如果您使用允许析构函数抛出的其他类型来编写模板,则预计会快速调用

std::terminate

此外,将类型插入标准库模板时要小心。大多数都会有未定义的行为,即来自所提供的用户类型的析构函数会抛出。

© www.soinside.com 2019 - 2024. All rights reserved.