Как спецификатор исключения в операторе присваивания перемещения может повлиять на конструктор перемещения?

Я тестировал GCC 5.2 и clang 3.6, оба в режиме С++ 14, и они дают одинаковый результат.

Для следующего кода

#include <iostream>
#include <type_traits>

struct S {
  // S& operator= (S&&) noexcept { return *this; }
};


int main() {
  std::cout << std::is_nothrow_move_constructible<S>::value
            << std::is_nothrow_move_assignable<S>::value;  
}

получен результат 11. Но если раскомментировать оператор присваивания перемещения, вывод станет 01. Как может явная спецификация noexcept оператора присваивания перемещения повлиять на конструктор перемещения?


person Lingxi    schedule 10.10.2015    source источник


Ответы (5)


Определив оператор присваивания перемещения, вы отключили конструктор перемещения из-за правило 5. Класс не является is_nothrow_move_constructible, потому что он вообще не может быть создан для перемещения, этот конструктор больше недоступен, если вы его не определите.

§12.8 Копирование и перемещение объектов класса

Если определение класса X явно не объявляет конструктор перемещения, он будет неявно объявлен как используемый по умолчанию тогда и только тогда, когда
X не имеет объявленного пользователем конструктора копирования,
X не имеет объявленный пользователем оператор присваивания копирования,
X не имеет объявленного пользователем оператора присваивания перемещения,
X не имеет объявленного пользователем деструктора и
— конструктор перемещения не будет неявно определен как удаленный.

В случае, когда у вас не было определяемого пользователем конструктора перемещения, оба были определены неявно и соответствовали приведенной ниже спецификации.

§15.4 Спецификации исключений

Неявно объявленная специальная функция-член должна иметь спецификацию исключения. Если f является неявно объявленным конструктором по умолчанию, конструктором копирования, конструктором перемещения, деструктором, оператором присваивания копирования или оператором присваивания перемещения, его неявная спецификация исключения указывает идентификатор типа T тогда и только тогда, когда T разрешено спецификацией исключения функция, напрямую вызываемая неявным определением f; f должен разрешать все исключения, если любая вызываемая им функция разрешает все исключения, и f не разрешает никаких исключений, если каждая вызываемая им функция не допускает исключений.

person Cory Kramer    schedule 10.10.2015

Объявив назначение перемещения, вы потеряли свой неявный конструктор перемещения.
См. полную схему ниже.

введите описание изображения здесь

person Trevor Hickey    schedule 10.10.2015
comment
Хороший график! Кстати, почему эти поля по умолчанию темно-красные? - person LogicStuff; 10.10.2015
comment
Потому что это крайне опасно. - person Puppy; 10.10.2015
comment
Можете дать ссылку? - person LogicStuff; 10.10.2015
comment
@LogicStuff Красные квадраты обозначают устаревшее поведение, начиная с C++98/03. Я считаю, что в более новых стандартах они не объявляются, но я не уверен, что на это есть гарантия. В моей версии clang они оказываются недекларированными. - person Trevor Hickey; 10.10.2015
comment
Думаю, я просто запомню первые три строки. - person Lingxi; 10.10.2015
comment
Вот ветка с участием Говарда Хиннанта, а не человека, составившего эту таблицу: Operations" title="каковы правила автоматического создания операций перемещения">stackoverflow.com/questions/24342941/ Презентация Говарда: accu.org/content/conf2014/Howard_Hinnant_Accu_2014.pdf - person Trevor Hickey; 10.10.2015
comment
Хороший график! Спасибо за ссылку. Красный означает, что он устарел в C++ 11 и более поздних версиях. Они не были удалены, но могут быть включены в будущий стандарт. Рекомендация: не полагайтесь на устаревшее поведение. Если вы явно объявляете любой из деструктора, конструктора копирования или присваивания копирования, то явно объявляйте и конструктор копирования, и присваивание копирования. - person Howard Hinnant; 10.10.2015

Конструктор перемещения в данном случае просто не генерируется — к noexcept он отношения не имеет.

Из cppreference:

Если для типа класса (структуры, класса или объединения) не предоставлены пользовательские конструкторы перемещения и выполняются все следующие условия:

  • нет объявленных пользователем конструкторов копирования
  • нет объявленных пользователем операторов присваивания копирования
  • нет объявленных пользователем операторов присваивания перемещения
  • нет объявленных пользователем деструкторов (до C++14)

неявно объявленный конструктор перемещения не определен как удаленный из-за условий, подробно описанных в следующем разделе, тогда компилятор объявит конструктор перемещения как неявный встроенный открытый член своего класса с сигнатурой T::T(T&&).

person Rostislav    schedule 10.10.2015

12.8/9:

Если определение класса X явно не объявляет конструктор перемещения, он будет неявно объявлен как заданный по умолчанию тогда и только тогда, когда

  • X не имеет объявленного пользователем конструктора копирования,

  • X не имеет объявленного пользователем оператора присваивания копии,

  • X не имеет объявленного пользователем оператора присваивания перемещения, и

  • X не имеет объявленного пользователем деструктора.

Объявляя оператор присваивания перемещения, вы вообще запрещаете классу иметь какой-либо конструктор перемещения.

person aschepler    schedule 10.10.2015

Определив оператор перемещения, вы подавили неявный конструктор перемещения. Вот почему std::is_nothrow_move_constructible терпит неудачу. Предоставьте его, чтобы получить желаемый результат:

struct S {
  S(S&&) noexcept {}
  S& operator= (S&&) noexcept { return *this; }
};
person user5431696    schedule 10.10.2015