可移动但不可复制的异常

Moveable but Non-Copyable Exceptions

本文关键字:异常 可复制 可移动      更新时间:2023-10-16

我正在考虑编写不可复制的异常类。我觉得这很有趣,因为这样我就不必担心在复制构造函数中分配时可能引发的异常。如果异常对象的创建成功,则一切正常,std::terminate应该不会出现问题。

struct exception
{
exception() = default;
exception(const exception&) = delete;
exception(exception&&) noexcept = default;
~exception() noexcept = default;
auto operator=(const exception&) -> exception& = delete;
auto operator=(exception&&) noexcept -> exception& = delete;
};
int main()
{
try {
try {
throw exception{};
} catch (...) {
std::rethrow_exception(
std::current_exception());
}
} catch (const exception& e) {
return 1;
}
}

GCC-4.7和Clang-3.2接受上述代码。然而,我有点惊讶。据我所知,有几种情况下可能会复制异常对象,例如std::current_exception()std::rethrow_exception()

问题:根据C++11,上述代码是否正确,即是否会被所有符合C++11的编译器接受?

编辑:std::rethrow_exceptionstd::current_exception添加到示例中。两个编译器都接受此版本。这应该清楚地表明,如果在抛出异常时编译器不需要复制构造函数,那么在使用这两个函数时编译器就不需要复制构造器。

current_exception表示它引用当前异常或它的副本,但没有说明是哪一个。这向我表明:

  • 未指定是否复制[*]
  • 因此,您的异常类不好(如果有人可能会调用它的current_exception,则肯定不会)
  • 因此,它在某些实现中工作也就不足为奇了。除非实现者对此有要求,或者希望实现者避免复制,否则可能不会对当前异常进行复制

只要扔东西并通过引用接住它就可以了。throw表达式中的临时"用于初始化"实现用于保持当前异常的对象,因此可以移动或复制(根据类支持的情况),并且可以消除该移动/复制。

就其价值而言,make_exception_ptr被指定为始终复制。您可能会认为这是一个缺陷:e可以是一个按值参数,在这种情况下移动可能会更好。但这是我冲动和无知的印象,我以前从未见过这些功能。

[*]current_exception是否"每次调用时都会创建一个新副本",这一点没有明确说明,但我不完全确定这是否意味着它在第一次调用时是否会创建新副本。

然而,我有点惊讶。据我所知,有几种情况下可能会复制异常对象,例如std::current_exception()std::rethrow_exception()

但你不会给他们中的任何一个打电话。该标准非常清楚如何初始化异常对象。从15.1开始,p3:

throw表达式初始化一个临时对象,称为异常对象,其类型是通过从throw操作数的静态类型中删除任何顶级cv限定符s并将类型分别从"T的数组"或"返回T的函数"调整为"指向T的指针"或"指向返回T的函式的指针"来确定的。temporary是一个左值,用于初始化匹配处理程序(15.3)中命名的变量。如果异常对象的类型是不完整类型或指向除void(可能是cv限定的)之外的不完整类型的指针,则程序格式错误。除了这些限制和15.3中提到的对类型匹配的限制外,throw的操作数在调用(5.2.2)或返回语句的操作数。

简而言之,它的作用类似于按值返回类型:返回值/异常对象由您提供的表达式初始化。因为您使用的表达式是一个临时表达式,所以它的作用就像从函数返回一个临时函数并调用move构造函数。诚然,这很有可能被忽略,但这就是15.1,p5的观点:

当抛出的对象是类对象时,即使复制/移动操作被取消,复制/移动构造函数和析构函数也应该是可访问的(12.8)

返回值也是如此:在适当的情况下,通过复制/移动初始化来初始化返回值。因此,适当的构造函数必须是可访问的,即使它们被省略了。

不能以需要异常对象的复制构造的方式抛出异常类。所以你不能抛出左值;您只能抛出prvalue或xvalue。

标准中没有任何地方规定允许系统无故任意复制异常。调用std::current_exception可能会复制它。调用std::rethrow_exception可能会复制。

但是,如果您不调用显式复制异常对象的东西,则不允许随意调用C++。