2017-02-03 54 views
5

是什么,在C++标准而言,预期的(如果有的话)下面的程序的输出:对于具有抛出拷贝构造函数和noexcept按值拷贝赋值的类,is_nothrow_copy_assignable的值是多少?

#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"; 
} 

换句话说,不将型性状值的分析看的声明只有赋值运算符,这是noexcept,并且它从而得到

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移动构造函数,一个投掷拷贝赋值和noexcept移动分配。

假设都复制和移动分配可以在交换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),我们只能安全地执行此操作。否则,依赖于这些类型特征的代码将表现得很差,我们的单个按值分配而不是是两个单独赋值运算符的正确替代。

+0

对于'vector',你需要'is_nothrow_move_assignable'是正确的,但不一定是'is_nothrow_copy_assignable'。也没有必要使用'boolalpha'两次。 –

+0

感谢您的评论。我已经编辑了相应的问题。当然,即使'std :: vector'不需要它,''nothrow_copy_assignable'仍然应该正确运行。 – jbab

+0

其实我很困惑。 'vector'要求'is_nothrow_move_constructible'是正确的,以在重新分配期间获得完全的效率。我不记得它是否真的需要'is_nothrow_move_assignable'。但是,是的,无论“vector”是否关心它,性状都应该正确。 –

回答

7

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操作员确定它的操作数,这是一个未计算的操作数(第5)的评价是否,可以抛出异常(15.1)。 [012]如果表达式(15.4)的潜在异常集为空,则noexcept运算符的结果为true,否则为false。

而在[except.spec]:

的异常规范noexceptnoexcept(constant-expression),其中恒定表达产量true,表示异常规范是空集。的异常规范noexcept(constant-expression),其中常数表达式产生false,或在除此以外的函数声明为析构函数(12.4)或解除分配函数(3.7.4.2)不存在异常规范的表示 异常规范,即所有类型的集合。

和:

集合的表达式e的潜在的异常的是空的,如果e是一个核心常量表达式(5.20)。否则, 它是e的直接子表达式的潜在异常集合的并集,包括在函数调用中使用的默认 参数表达式,与由e形式定义的集合S组合,如下所示:[。 ..]
- 如果e隐式调用一个或多个函数(例如重载操作符,新表达式中的分配函数或者e是全表达式(1.9)的析构函数),则S是:
      - 在所有这些功能的异常规范类型集,并
      - 如果e是一个新的表达式[...]

现在,一个A的从A const&转让涉及两个步骤:

  1. 调用的A
  2. 拷贝构造函数调用的A

异常规范的拷贝赋值运算符是这两个函数的所有异常规范的联合,这是所有类型的集合 - 因为复制构造函数没有例外规范。因此,is_nothrow_copy_assignable_v<A>应该是false。 gcc是正确的。

+0

问题实际上是什么“赋值”意味着:单独调用赋值运算符或整个赋值表达式。如果后者意味着(而且是),我认为没有任何疑问。 –

+0

@ T.C。我认为,如果它特别指定了赋值运算符,那么它会这么说 - 而且这样做反正也不会很实际。 – Barry