2017-02-03 11 views
5

Что, с точки зрения стандарта С ++, ожидаемый (если таковые имеются) выход следующей программы:Какова ценность is_nothrow_copy_assignable для класса с металическим конструктором копирования и присваиванием копии noexcept by-value?

#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 

Или это учитывать контекст вызова (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, Update 3 дает

true 
true 

, тогда как GCC 6.1 дает

false 
true 

Кто такой?

фон

Ситуация, как это происходит, когда у нас есть управляющий ресурсом класса с конструктором метания копий (с выделением ресурсов может произойти сбой), шаг конструктор noexcept, назначение метания копий и назначение noexcept перемещения ,

Предположив, что и копирование и перемещение назначения может быть эффективно реализован с точки зрения обмена ИПЧМ:

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

Спасибо за замечания. Я соответствующим образом отредактировал вопрос. Конечно, '' s_nothrow_copy_assignable' должен по-прежнему вести себя корректно, даже если 'std :: vector' его не требует. – jbab

+0

На самом деле, я смутился. 'vector' требует, чтобы' is_nothrow_move_constructible' был правильным, чтобы получить полную эффективность при перераспределении. Я не могу вспомнить, если что-то в нем действительно хочет 'is_nothrow_move_assignable'. Но да, независимо от того, заботится ли об этом «вектор», черты должны вести себя правильно. –

ответ

7

Определение is_nothrow_copy_assignable в [meta.unary.prop]:

Для Referenceable типа T, к тому же результату, как is_nothrow_assignable_v<T&, const T&>, в противном случае false.

Ok, 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). [...] Результат оператора noexcept равен true, если набор потенциальных исключений выражения (15.4) пуст, а false в противном случае.

А в [except.spec]:

Исключение-спецификация noexcept или noexcept(constant-expression), где константа-выражение дает true, обозначает спецификацию исключения, который является пустым множеством. Спецификация исключения noexcept(constant-expression), где константное выражение дает false, или отсутствие спецификации исключения в деклараторе функции, отличном от такового для деструктора (12.4) или функции освобождения (3.7.4.2), обозначает спецификация исключения, которая является набором всех типов.

И:

Множество возможных исключений из выражения е пуст, если е является постоянным выражением ядра (5,20). В противном случае, это объединение множеств потенциальных исключений из непосредственных подвыражений e, включая аргументы аргументов по умолчанию , используемые в вызове функции, в сочетании с множеством S, определяемым формой e, следующим образом: [. ..]
- Если e неявно вызывает одну или несколько функций (например, перегруженный оператор, функцию распределения в новом выражении или деструктор, если e является полным выражением (1.9)), S является объединением :
      - множества типов в спецификации исключений из всех таких функций, и
      - если е новое выражение [...]

Теперь Цессия A из A const& включает два этапа:

  1. Призывая копию конструктора A
  2. вызова оператора присваивания от копирования A

Спецификация исключения является объединением всех спецификаций исключений обеих этих функций, который является множеством всех типов, поскольку конструктор копирования не имеет исключение-спецификация.

Следовательно, is_nothrow_copy_assignable_v<A> должно быть false. gcc верен.

+0

Вопрос в том, что означает «назначение»: вызов только оператора присваивания или всего выражения присваивания. Я не думаю, что есть какие-либо сомнения в том, что он должен возвращать false, если это подразумевается (и это так). –

+0

@ T.C. Я думаю, если бы это означало конкретно оператора присваивания, он бы сказал это - и это было бы не очень практично. – Barry