【问题标题】:Guaranteed copy elision and deleted copy/move constructor when throwing an exception抛出异常时保证复制省略和删除复制/移动构造函数
【发布时间】:2021-08-09 03:38:42
【问题描述】:

自 C++17 以来,prvalue 的含义发生了变化,这使得在某些情况下可以保证复制省略。从 cppreference 开始,复制/移动构造函数在这种情况下不需要存在或可访问。

当抛出异常时,异常对象被复制初始化,复制/移动可能会受到复制省略。但是否要求此时复制/移动构造函数必须可用?

来自[except.throw]

当抛出的对象是类对象时,为复制初始化选择的构造函数以及考虑抛出的对象为左值的复制初始化选择的构造函数应该是不可删除和可访问的,即使如果复制/移动操作被省略 ([class.copy.elision])。析构函数可能被调用([class.dtor])。

标准中提到相应的构造函数必须是不可删除且可访问的,即使它被省略了。

但是我测试发现GCC和Clang都允许在删除对应的构造函数时抛出:

struct A {
    A() = default;
    A(A&&) = delete;
};

try {
    throw A{};    // copy elision
} catch(...) {}

代码可以编译,但似乎不符合标准的要求。如果我将版本从 C++17 降低到 C++14,它们都会报错:

struct A {
    A() = default;
    A(A&&) = delete;
};

try {
    throw A{};    // error: call to deleted constructor of 'A'
} catch(...) {}

是我对标准的理解有误,还是只是编译器放宽了限制?

【问题讨论】:

    标签: c++ c++17 language-lawyer


    【解决方案1】:

    只要您对该段的分析实际适用,您对标准的理解是正确的。

    但它不适用,因为 no 构造函数曾被考虑用于复制初始化。

    保证省略有点用词不当;这是对该概念的有用解释,但就标准而言,它并不能准确地反映它的工作原理rewriting the meaning of prvalues so there never is a copy/move to be elided in the first place 保证省略有效。

    A a = A{}; 是复制初始化,但它甚至假设 调用复制/移动构造函数。变量ainitialized by the prvalue's initializer

    prvalue 的结果是表达式存储到其上下文中的值。结果为值 V 的纯右值有时被称为具有或命名为值 V。纯右值的 结果对象是由纯右值初始化的对象

    a是prvalue初始化的“结果对象”。

    这里也一样。 A{} 是纯右值。异常对象是由纯右值初始化的“结果对象”。没有临时对象,没有考虑使用的复制/移动构造函数。

    【讨论】:

    • 谢谢。我已经明白你的回答了。那么本例中选择的构造函数只是默认构造函数?另外,您能否解释或举例说明“为复制初始化选择的构造函数将抛出的对象视为左值”?
    【解决方案2】:

    首先,C++17 在创建异常对象和激活异常处理程序时都不会保证复制省略。

    复制构造函数存在的要求实际上很简单——一方面,复制省略在某些情况下是不明智的,包括抛出和捕获异常;另一方面,编译器只被允许执行省略但没有义务。所以编译器会在 case 中使用构造函数。

    您的代码没有实现案例这一事实并不意味着您的类型(类)被允许不支持这些案例(即使编译器可以成功编译它)。这些案例对于该语言仍然有效,并且必须能够在不修改用于异常的类型(类)的代码的情况下实现。

    【讨论】:

      猜你喜欢
      • 2014-01-02
      • 2023-03-03
      • 1970-01-01
      • 1970-01-01
      • 2018-09-17
      • 2019-09-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多