Как спецификатор исключения в операторе присваивания перемещения может влиять на конструктор перемещения?
Я тестирую с GCC 5.2 и clang 3.6, оба в режиме C++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
спецификация на оператор присваивания перемещения может повлиять на конструктор перемещения?
5 ответов
Определив оператор присваивания перемещения, вы отключили конструктор перемещения из-за правила 5. Класс не is_nothrow_move_constructible
так как он вообще не может быть перемещаем, этот конструктор больше не доступен, пока вы его не определите.
§12.8 Копирование и перемещение объектов класса
Если определение класса
X
явно не объявляет конструктор перемещения, он будет неявно объявлен как дефолт, если и только если
-X
не имеет объявленного пользователем конструктора копирования,
-X
не имеет заявленного пользователем оператора копирования,
-X
не имеет объявленного пользователем оператора назначения перемещения,
-X
не имеет объявленного пользователем деструктора, и
- конструктор перемещения не будет неявно определен как удаленный.
В случае, когда у вас не было пользовательского конструктора перемещения, оба были определены неявно и следовали приведенной ниже спецификации.
§15.4 Спецификации исключений
Неявно объявленная специальная функция-член должна иметь спецификацию исключения. Если
f
является неявно объявленным конструктором по умолчанию, конструктором копирования, конструктором перемещения, деструктором, оператором назначения копирования или оператором назначения перемещения, его неявная спецификация исключения определяет идентификатор типаT
если и только еслиT
допускается спецификацией исключений для функции, непосредственно вызываемойf
неявное определение;f
должен разрешать все исключения, если любая функция, которую он непосредственно вызывает, разрешает все исключения, иf
не должно допускать никаких исключений, если каждая функция, которую она непосредственно вызывает, не допускает никаких исключений.
Объявив о назначении перемещения, вы потеряли неявный конструктор перемещения.
Смотрите полную таблицу ниже.
12.8/9:
Если определение класса
X
явно не объявляет конструктор перемещения, он будет неявно объявлен как дефолт, если и только если
X
не имеет объявленного пользователем конструктора копирования,
X
не имеет заявленного пользователем оператора копирования,
X
не имеет объявленного пользователем оператора назначения перемещения, и
X
не имеет объявленного пользователем деструктора.
Объявляя оператор присваивания перемещения, вы вообще не допускаете, чтобы у класса был какой-либо конструктор перемещения.
Конструктор перемещения просто не генерируется в этом случае - он не имеет ничего общего с noexcept
,
Из контекста:
Если для типа класса (struct, class или union) не заданы пользовательские конструкторы перемещения, и все из следующего верно:
- нет никаких заявленных пользователем конструкторов копирования
- нет заявленных пользователем операторов копирования
- нет пользовательских операторов перемещения
- нет деструкторов, объявленных пользователем (до C++14)
неявно объявленный конструктор перемещения не определен как удаленный из-за условий, подробно описанных в следующем разделе, тогда компилятор объявит конструктор перемещения как неявный встроенный открытый член своего класса с подписью T::T(T&&).
Определив оператор перемещения, вы подавили неявный конструктор перемещения. Вот почему std::is_nothrow_move_constructible
выходит из строя. Предоставьте его, чтобы получить желаемый результат:
struct S {
S(S&&) noexcept {}
S& operator= (S&&) noexcept { return *this; }
};