我正在考虑编写不可复制的异常类。我发现它很有趣,因为那时我不必担心在复制构造函数中的分配期间可能抛出的异常。如果异常对象的创建成功,一切都很好,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_exception
和std::current_exception
。两个编译器都接受这个版本。这应该清楚地表明,如果编译器在抛出异常时不需要复制构造函数,编译器在使用这两个函数时将不需要一个。
答案 0 :(得分:2)
current_exception
表示它指的是当前的异常或它的副本,但没有说明哪个。这告诉我:
current_exception
)抛出东西并通过引用捕获它很好。 throw
表达式中的临时“用于初始化”实现用来保持当前异常的对象,因此可以移动或复制它(根据类支持),并且移动/复制可以被剔除。
对于它的价值,make_exception_ptr
被指定为始终复制。您可能会认为这是一个缺陷:e
可以是一个按值参数,在这种情况下,移动可能会更好。但这是我浮躁而无知的印象,我以前从未见过这些功能。
[*]显然没有说明current_exception
“每次被称为”都会创建一个新副本“,但我不完全确定这是否意在暗示它是否未指明是否创建了新副本它第一次被召唤。
答案 1 :(得分:2)
然而,我有点惊讶。据我所知,有几种情况可能会复制异常对象,例如:
std::current_exception()
和std::rethrow_exception()
。
但是你不要打电话给他们。该标准非常清楚异常对象的初始化方式。从15.1开始,p3:
throw-expression初始化一个临时对象,称为异常对象,其类型是通过从throw的操作数的静态类型中删除任何顶级cv-qualifier并从“array of”数组中调整类型来确定的。 T“或”函数返回T“到”指向T的指针“或”指向函数返回T的指针“。临时是一个左值,用于初始化匹配处理程序中命名的变量(15.3)。如果异常对象的类型是不完整类型或指向不完整类型的指针(可能是cv-qualified),则程序格式不正确。除了这些限制和15.3中提到的类型匹配的限制之外,throw的操作数被完全视为一个函数参数。 call(5.2.2)或return语句的操作数。
简而言之,它的作用类似于按值返回类型:返回值/异常对象由您提供的表达式初始化。因为您使用的表达式是临时的,所以它就像从函数返回临时表并调用移动构造函数一样。当然,赔率很高,但这就是15.1,p5的重点:
当抛出的对象是类对象时,即使复制/移动操作被省略,也应该可以访问复制/移动构造函数和析构函数(12.8)。
返回值也是如此:在适当的情况下,通过复制/移动初始化初始化返回值。因此,即使它们被省略,也必须能够获得适当的构造函数。
您不能以需要复制构造异常对象的方式抛出异常类。所以你不能抛出一个左值;你只能抛出一个prvalue或一个xvalue。
标准中没有任何地方说系统被允许任意无理由地复制异常。致std::current_exception
的电话可能会复制它。拨打std::rethrow_exception
可能会复制它。
但是如果你不调用那些明确复制你的异常对象的东西,那么不允许 这样做是不可能的。