根据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
还是考虑了调用上下文(a
、b
是A
的实例)
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〕中:
异常规范
noexcept
或noexcept(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
涉及两个步骤:
- 调用
A
的复制构造函数 - 调用
A
的复制分配运算符
异常规范是这两个函数的所有异常规范的并集,这是所有类型的集合,因为复制构造函数根本没有异常规范。
因此,is_nothrow_copy_assignable_v<A>
应该是false
。gcc是正确的。