为什么std智能指针类型的析构函数没有继承所指对象的noexcept析构函数状态?

6
在C++11中,我的理解是默认情况下析构函数隐式地被声明为noexcept(true),但有一个例外:如果我有一个类C,它的析构函数显式地标记为noexcept(false)(可能是因为某种奇怪的原因它会抛出异常,我知道你不应该这样做,也知道为什么),那么从C派生或包含类型为C的任何类的析构函数也变为noexcept(false)
然而,包含std::shared_ptr<C>的类显然不会自动切换其析构函数为noexcept(false),对于包含std::weak_ptr<C>std::unique_ptr<C>等也是如此。
以下是一个完整的示例:
#include <type_traits>
#include <memory>

struct Normal {
    ~Normal() {
    }
};

struct ThrowsInDtor {
    ~ThrowsInDtor() noexcept(false) {
        throw 42;
    }
};

template<typename T>
struct Wrapper {
    T t;
};

template<typename T>
struct UniquePtrWrapper {
    std::unique_ptr<T> t;
};

template<typename T>
struct SharedPtrWrapper {
    std::shared_ptr<T> t;
};

static_assert(std::is_nothrow_destructible<Normal>::value, "A"); // OK
static_assert(!std::is_nothrow_destructible<ThrowsInDtor>::value, "B"); // OK

static_assert(std::is_nothrow_destructible<Wrapper<Normal>>::value, "C"); // OK
static_assert(!std::is_nothrow_destructible<Wrapper<ThrowsInDtor>>::value, "D"); // OK

static_assert(std::is_nothrow_destructible<UniquePtrWrapper<Normal>>::value, "E"); // OK
static_assert(!std::is_nothrow_destructible<UniquePtrWrapper<ThrowsInDtor>>::value, "F"); // FAILS

static_assert(std::is_nothrow_destructible<SharedPtrWrapper<Normal>>::value, "G"); // OK
static_assert(!std::is_nothrow_destructible<SharedPtrWrapper<ThrowsInDtor>>::value, "H"); // FAILS

我觉得F和H的失败很奇怪。我本以为所拥有/引用类型的析构函数的noexcept状态会通过类似于在智能指针析构函数声明中使用noexcept(std::is_nothrow_destructible<T>::value)的noexcept表达式来传播到智能指针析构函数。

然而,标准并没有提到这一点,我查看的标准库代码也没有这样做。

是否有人知道为什么标准智能指针不会将实例化类型的noexcept析构函数状态传播到智能指针析构函数?

1个回答

6

std::shared_ptr<T>旨在与不完整的T一起使用,因此在声明其析构函数时没有办法获取您要求的信息。此外,您可以这样做:

std::shared_ptr<void> dummy = std::make_shared<T>(); // for some complete T

现在,对于std::shared_ptr<void>noexcept应该怎么表达呢?这是从std::shared_ptr的角度来看的运行时信息。
对于std::unique_ptr,有以下内容:

20.7.1.2.2 unique_ptr析构函数 [unique.ptr.single.dtor]

1 ~unique_ptr();

要求: 表达式 get_deleter()(get()) 应该良好定义,且不会抛出异常。[ 注意:使用default_delete时需要T为完整类型。—endnote]

这意味着删除器需要确保不会抛出异常,这并不一定取决于T的析构函数,例如,当使用空删除器时。

这并没有涉及到std::unique_ptr,然而,它需要在析构函数被实例化和调用的时候类型是完整的。 - Xeo
@Xeo 它甚至不一定会调用它所指向的对象的析构函数。现在我想起来,对于 std::shared_ptr 也可能是这样。无论如何,我已经更新了答案。 - Daniel Frey
我指的是std::unique_ptr本身的析构函数,抱歉造成困惑。 - Xeo
1
@Xeo,没有混淆,那部分很清楚。关键是即使在那种情况下,您也无法添加“正确”的noexcept说明(在OPs意义上):只需想想std::unique_ptr<std::shared_ptr<T>>。我的脑袋转得飞快,我认为这是a)现在去睡觉的一个信号,b)有很好的理由不让析构函数抛出异常 :) - Daniel Frey

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接