【问题标题】:Should I declare the copy constructor of my exceptions noexcept?我应该声明我的异常 noexcept 的复制构造函数吗?
【发布时间】:2014-03-05 20:49:16
【问题描述】:

更有效的 C++ 中,Scott Meyers 说

C++ 指定复制作为异常抛出的对象。

然后我想,如果复制构造函数依次抛出异常,std::terminate 被调用,所以这是声明我所有异常的复制构造函数 noexcept 的一个很好的理由(而且,我猜,不抛出从堆中分配内存的对象,例如std::string)。

然而,令我惊讶的是,GCC 4.7.1 附带的标准库实现没有为 std::bad_allocstd::exception 定义那些复制构造函数。他们不应该定义它们noexcept吗?

【问题讨论】:

  • 仅供参考,GCC 4.7.1 于 9 年前发布。

标签: c++ exception c++11 copy-constructor noexcept


【解决方案1】:

它们必须是 noexcept,因为 C+11 和 throw() 在 C++11 之前(https://en.cppreference.com/w/cpp/error/exception/exceptionhttps://en.cppreference.com/w/cpp/memory/new/bad_alloc),以及其他标准库例外。 如果不是except,而是throw(),这只是表示编译器版本在该部分不支持C++11,而是按照之前版本的语言。 如果它们既不是 noexcept 也不是 throw(),这意味着编译器的版本既不能正确实现 C++11 也不能正确实现该语言的早期版本。

至于

C++ 指定复制作为异常抛出的对象。

, 由于 C++11(在 C++11 之前,标准没有规范这个问题)在某些情况下允许编译器(尽管没有义务)在抛出和捕获异常时省略复制,并且在某些情况下编译器(自 C++ 11)在抛出而不是复制时必须移动到异常对象。 因此,尽管存在编译器在抛出时必须复制对象的情况,但在其他情况下,可能会发生抛出时没有复制的情况,以及尽管存在编译器必须在捕获时复制异常对象的情况,但也可能发生这种情况没有复制然后捕捉。详细在这里:https://en.cppreference.com/w/cpp/language/copy_elision

此外,您应该避免从构造函数中抛出异常并移动用于异常的类型(类)的构造函数,因为它们可以在抛出异常时被调用(这里有很多编译器选择的东西 -查看相同的链接https://en.cppreference.com/w/cpp/language/copy_elision) 并抛出额外的异常将阻止抛出最初的异常,这将丢失。 抛出时复制构造函数的异常也会导致相同的结果。

至于你是否应该声明你的复制构造函数 noexcept,

是的,您应该将其声明为“noexcept” - 这是性能问题。 但是这里的主要问题是你必须保证它实际上不会抛出异常。

【讨论】:

    【解决方案2】:

    Section 18.8.1 [exception]/p1 规定:

    namespace std {
        class exception {
        public:
          exception() noexcept;
          exception(const exception&) noexcept;
          exception& operator=(const exception&) noexcept;
          virtual ~exception();
          virtual const char* what() const noexcept;
      };
    }
    

    即std::exception 的复制构造函数和复制赋值应为noexcept,可通过以下方式进行测试:

    static_assert(std::is_nothrow_copy_constructible<std::exception>::value, "");
    static_assert(std::is_nothrow_copy_assignable<std::exception>::value, "");
    

    即如果一个实现没有使这些成员成为 noexcept,那么它在这方面是不符合的。

    同样,18.6.2.1 [bad.alloc]/p1 也指定了 noexcept 复制:

    namespace std {
           class bad_alloc : public exception {
           public:
             bad_alloc() noexcept;
             bad_alloc(const bad_alloc&) noexcept;
             bad_alloc& operator=(const bad_alloc&) noexcept;
             virtual const char* what() const noexcept;
      };
    }
    

    此外,所有标准定义的异常类型都具有 noexcept 复制成员,无论是显式的还是隐式的。对于&lt;stdexcept&gt; 中定义的类型,通常使用what() 字符串的引用计数缓冲区来实现。这在 [exception]/p2 中有明确说明:

    从类异常派生的每个标准库类T 应具有可公开访问的复制构造函数和可公开访问的 不以 例外。 ...

    也就是说,在一个高质量的实现中(并且在这方面创建一个高质量的实现并不需要英雄主义),异常类型的副本成员不仅不会抛出异常(自然是因为它们被标记为noexcept) ,他们也不会打电话给terminate()

    复制标准定义的异常类型没有失败模式。要么没有要复制的数据,要么数据是引用计数且不可变的。

    【讨论】:

    • 所以我读到这一点是对的,“不会因异常退出”这句话允许一个糟糕的实现调用 std::terminate (即在主体中抛出异常,结果的 noexcept 调用终止)?
    • @JohannesSchaub-litb:我最近阅读了另一个 SO 问题的答案,该问题提出 std::chrono::seconds 的构造函数可能会调用 this_thread::sleep。所以,本着“一个如此荒谬的实现,它的上市时间将以毫秒为单位”的精神,是的,当然。该标准不是一个完美的文件,并且永远无法 100% 防范这种荒谬。我希望它甚至不会尝试。它还有很多重要的事情要做。
    • 这并不能回答问题,不是吗? OP 将“我的异常”放在标题中,意思是用户定义的异常类。您只是在谈论标准定义的异常类。也许您只需要添加一些建议来遵循标准的示例。
    • noexcept 不会阻止尝试抛出异常,这将导致调用std::terminate
    【解决方案3】:

    异常的内存分配是在常规通道之外完成的:

    15.1 抛出异常 [except.throw]

    3 抛出异常复制初始化(8.5、12.8)临时 对象,称为异常对象。 [...]

    4 异常对象的内存是 以未指定的方式分配,除非在 3.7.4.1 中注明。 [...]

    3.7.4.1 分配函数[basic.stc.dynamic.allocation]

    4 全局分配函数仅作为新的结果调用 表达式(5.3.4),或直接使用函数调用语法调用 (5.2.2),或通过调用中的函数间接调用 C++ 标准库。 [注:特别是全局分配 没有调用函数来为 [...] 为异常对象 (15.1) 分配存储空间。 —结束注释]

    大多数实现都有一个单独的内存区域,从中分配异常对象,因此即使您重新抛出 std::bad_alloc 异常对象,也不会要求耗尽的空闲存储本身分配复制的异常对象。所以不应该有理由让复制本身产生另一个异常。

    【讨论】:

    • 在为异常对象分配内存的同时,将异常对象分配到特定的内存区域并保证没有异常,与对象复制构造函数是否使用常规内存分配无关。考虑异常对象复制其使用默认内存分配器类型的 std::string 成员 - 您仍然有可能使用 std::bad_alloc。此外,除了 std::bad_alloc 之外,还存在其他类型的异常。
    【解决方案4】:

    好吧,声明它noexcept 很好,但它要求你可以保证它不会抛出异常(对于可移植代码,在所有它的实现中!)。我希望这就是标准没有以这种方式声明的原因。

    声明复制构造函数noexcept 显然没有坏处,但尝试实现这一点可能会受到很大限制。

    【讨论】:

    • 标准不应该保证其异常的复制构造函数不会抛出吗?
    • 这可能是不可能的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-10-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-05-04
    • 1970-01-01
    相关资源
    最近更新 更多