Почему конструктор перемещения не объявлен и не удален с помощью clang?
Рассмотрим следующие классы.
struct with_copy {
with_copy() = default;
with_copy(with_copy const&) {}
with_copy& operator=(with_copy const&) { return *this; }
};
struct foo {
with_copy c;
std::unique_ptr<int> p;
};
- Имеет ли
with_copy
конструктор копирования? Да. Он был явно определен.
- Есть ли
with_copy
конструктор перемещения? Нет. Явный конструктор копирования предотвращает его создание.
- Имеет ли
with_copy
конструктор удаленных перемещений? Нет. Отсутствие конструктора перемещения не совпадает с удаленным. Конструктор с удаленным ходом попытается переместить плохо сформированный, а не дегенерировать на копию.
- Скопирован ли
with_copy
? Да. Его копия используется для копирования.
- Является ли
with_copy
подвижным? Да. Его конструктор копирования используется для перемещений.
... и теперь сложные.
- Имеет ли
foo
конструктор копирования? Да. Он имеет удаленный, поскольку его определение по умолчанию будет плохо сформировано из-за вызова unique_ptr
конструктора удаленных копий.
- Есть ли
foo
конструктор перемещения? GCC говорит "да", clang говорит "нет" .
- Имеет ли
foo
конструктор удаленных перемещений? И GCC, и clang говорят "нет" .
- Скопирован ли
foo
? Нет. Конструктор его копии удален.
- Является ли
foo
подвижным? GCC говорит "да", clang говорит "нет" .
(Поведение аналогично, если рассматривать назначение вместо построения.)
Насколько я вижу, GCC верен. foo
должен иметь конструктор перемещения, который выполняет перемещение по каждому элементу, который в with_copy
случае вырождается в копию. Поведение Клана кажется довольно смешным: у меня есть совокупность с двумя подвижными членами, и все же моя совокупность - это неподвижный кирпич.
Кто прав?
Ответы
Ответ 1
С++ 11, или, скорее, n3485, [class.copy]/9:
Если определение класса X
явно не объявляет конструктор перемещения, оно будет объявлено неявно как по умолчанию, если и только если
-
X
не имеет объявленного пользователем конструктора копирования, -
X
не имеет объявленного пользователем оператора копирования копии, -
X
не имеет объявленного пользователем оператора назначения перемещения, -
X
не имеет объявленного пользователем деструктора и - конструктор перемещения не будет неявно определен как удаленный.
и/11:
Неявно объявленный конструктор copy/move является членом inline public
своего класса. По умолчанию копия / move constructor для класса X
определяется как удаленный (8.4.3), если X
имеет:
- [...]
- для конструктора копирования, нестатического элемента данных ссылочного типа rvalue или
- для конструктора перемещения, нестатического элемента данных или прямого или виртуального базового класса с типом, который не имеет конструктора перемещения и не может быть тривиально скопирован.
Поскольку with_copy
не является тривиально-скопируемым, foo
не будет иметь конструктор move (он будет определен как удаленный, поэтому он не будет объявлен неявно).
С++ 1y, или скорее github repo совершить e31867c0 с 2013-11-12; включая DR1402:
/9:
Если определение класса X
явно не объявляет ход конструктор, одно будет объявлено как неявное, если и только если
-
X
не имеет объявленного пользователем конструктора копирования, -
X
не имеет объявленного пользователем оператора копирования копии, -
X
не имеет объявленного пользователем оператора назначения перемещения и -
X
не имеет объявленного пользователем деструктора.
и/11:
Неявно объявленный конструктор копирования/перемещения является inline public
член его класса. По умолчанию конструктор copy/move для класса X
определяется как удаленный (8.4.3), если X
имеет:
- [...]
- для конструктора копирования, нестатический член данных ссылочного типа rvalue.
Конструктор перемещения по умолчанию, который определяется как удаленный, игнорируется разрешение перегрузки (13,3, 13,4).
Здесь foo
будет иметь конструктор move.
Ответ 2
Я не совсем уверен, что вы протестировали, но он foo
, безусловно, движется назначаемым и перемещается конструктивно. По общему признанию, это ничего не говорит о том, что конструктор перемещения или доступ к перемещению доступны, просто работает конструкция или присвоение из rvalue. Оба clang (clang version 3.5 (trunk 196718)) и gcc (версия gcc 4.9.0 20131031 (экспериментальная) (GCC)) согласна с этой оценкой. Это полный источник, который я пробовал:
#include <iostream>
#include <type_traits>
#include <memory>
struct with_copy {
with_copy() = default;
with_copy(with_copy const&) {}
with_copy& operator=(with_copy const&) { return *this; }
};
struct foo {
with_copy c;
std::unique_ptr<int> p;
};
int main()
{
std::cout << "move constructible: "
<< std::is_move_constructible<foo>::value << '\n';
std::cout << "move assignable: "
<< std::is_move_assignable<foo>::value << '\n';
foo f0;
foo f1 = std::move(f0);
f0 = std::move(f1);
}