3

我正在考虑编写不可复制的异常类。我觉得这很有趣,因为这样我就不必担心在复制构造函数中分配期间可能引发的异常。如果异常对象的创建成功,一切都很好,应该没有问题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示例中。两个编译器都接受这个版本。这应该清楚地表明,如果编译器在引发异常时不需要复制构造函数,那么在使用这两个函数时编译器将不需要复制构造函数。

4

2 回答 2

2

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

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

只是扔东西并通过参考抓住它就可以了。表达式中的临时throw变量“用于初始化”实现所使用的对象以保持当前异常,因此可以移动或复制它(根据类支持),并且可以省略移动/复制。

对于它的价值,make_exception_ptr被指定为始终复制。您可能会争辩说这是一个缺陷:e可以是一个按值参数,在这种情况下移动可能会更好。但这是我的浮躁和无知的印象,我以前从未见过这些功能。

[*] 是否current_exception“每次调用时创建一个新副本”是明确未指定的,但我不确定这是否意味着它是否在第一次调用时创建一个新副本是未指定的。

于 2012-10-10T16:45:28.057 回答
2

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

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

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

简而言之,它就像按值返回类型:返回值/异常对象由您提供的表达式初始化。因为您使用的表达式是临时的,所以它的行为就像从函数返回一个临时并调用移动构造函数。当然,这很可能会被忽略,但这就是 15.1, p5 的意义所在:

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

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

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

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

但是如果你不调用显式复制你的异常对象的东西,C++ 就不允许这样做。

于 2012-10-10T18:32:52.093 回答