对于具有抛出复制构造函数和noexcept-by-value复制赋值的类,is_nothrow_copy_assigna

What is the value of is_nothrow_copy_assignable for a class with a throwing copy constructor and a noexcept by-value copy assignment?

本文关键字:复制 is copy nothrow assigna noexcept-by-value 于具 构造函数 赋值      更新时间:2023-10-16

根据C++标准,以下程序的预期输出(如果有的话)是什么:

#include <iostream>
#include <iomanip>
#include <type_traits>
class A {
public:
A() = default;
~A() = default;
A(A const& other) {}
A(A&& other) noexcept {}
A& operator=(A other) noexcept { return *this; }
};
int main() {
std::cout << std::boolalpha
<< std::is_nothrow_copy_assignable<A>::value << "n"
<< std::is_nothrow_move_assignable<A>::value << "n";
}

换句话说,类型特征值的评估是否只考虑赋值运算符的声明,这是不例外的,因此是否会产生

true
true

还是考虑了调用上下文(abA的实例)

a = b;            // may throw, implicitly calls copy c'tor
a = std::move(b); // noexcept, implicitly calls move c'tor

它能产生吗

false
true

实际尝试

使用Visual Studio 2015运行代码,更新3提供

true
true

而gcc 6.1给出了

false
true

谁是对的?

背景

当我们有一个带有抛出复制构造函数(因为资源分配可能会失败)、noexcept移动构造函数、抛出复制赋值和noexception移动赋值的资源管理类时,就会出现这样的情况。

假设复制和移动分配都可以根据交换idom:有效地实现

A& operator=(A const& other) {
A(other).swap(*this); // calls the copy c'tor, may throw
return *this;
}
A& operator=(A&& other) noexcept {
A(std::move(other)).swap(*this); // calls noexcept move c'tor
return *this;
}

然后我们可以考虑将两者压缩为按值拷贝分配

A& operator=(A other) noexcept {
other.swap(*this);
return *this;
}

然而,只有当std::is_nothrow_copy_assignable<A>std::is_nothrow_move_assignable<A>提供正确的值(分别为false和true)时,我们才能安全地做到这一点。否则,依赖于这些类型特征的代码将表现不佳,并且我们的单值赋值将而不是是两个独立赋值运算符的正确替代。

is_nothrow_copy_assignable的定义在[meta.unary.prop]中:

对于可引用类型T,结果与is_nothrow_assignable_v<T&, const T&>相同,否则为false

好的,A是可引用的(意味着A&是有效的)。所以我们进入is_nothrow_assignable:

is_assignable_v<T, U>true,已知赋值不会引发任何异常(5.3.7)。

is_assignable_v<A, A const&>肯定是true,所以我们满足第一部分。众所周知不抛出任何异常意味着什么?根据[expr.unary.noexcept]:

noexcept运算符确定其操作数的求值是否可以引发异常(15.1),该操作数是未求值的操作数(第5条)。[…]如果表达式(15.4)的潜在异常集为空,则noexcept运算符的结果为true,否则为false。

并且在〔except.spec〕中:

异常规范noexceptnoexcept(constant-expression),其中常量表达式产生true,表示作为空集的异常规范。异常规范noexcept(constant-expression),其中常量表达式产生false,或者在除析构函数(12.4)或释放函数(3.7.4.2)之外的函数声明符中不存在异常规范表示异常规范,它是所有类型的集合。

和:

如果e是核心常量表达式(5.20),则表达式e的潜在异常集为空。否则,它是e的直接子表达式的潜在异常集的并集,包括default函数调用中使用的参数表达式,与e形式定义的集合S组合,如下所示:[…]
--如果e隐式调用一个或多个函数(例如重载运算符、新表达式中的分配函数,或者如果e是完整表达式(1.9)则为析构函数),则S是以下函数的并集:
 nbsp --所有此类函数的异常规范中的类型集,以及
nbsp nbsp;--如果e是一个新的表达式〔…〕

现在,从A const&分配A涉及两个步骤:

  1. 调用A的复制构造函数
  2. 调用A的复制分配运算符

异常规范是这两个函数的所有异常规范的并集,这是所有类型的集合,因为复制构造函数根本没有异常规范

因此,is_nothrow_copy_assignable_v<A>应该是false。gcc是正确的。