Конструктор/назначение конструктора по умолчанию и удаленный конструктор/назначение копии
Согласно стандарту,
Если определение класса X явно не объявляет конструктор перемещения, оно будет объявлено как неявное как дефолтное, если и только если
- X не имеет объявленного пользователем конструктора копирования,
- X не имеет объявленного пользователем оператора назначения копирования,
- X не имеет объявленного пользователем оператора назначения перемещения и
- X не имеет объявленного пользователем деструктора.
Теперь следующее не удается скомпилировать
# include <utility>
class Foo
{
public:
Foo() = default;
Foo(Foo const &) = delete;
};
int main()
{
Foo f;
Foo g(std::move(f)); // compilation fails here
return 0;
}
Таким образом, кажется, что удаленная функция считается определяемой пользователем, что имеет смысл (это не стандартная реализация). Тем не менее, в этом конкретном случае, как удалить конструктор/присвоение конструктора переходов/конструктора беспорядка копирования?
Я думаю, что этот вопрос имеет практическое значение, потому что ручная генерация и особенно. поддержание таких функций по умолчанию является склонным к ошибкам, в то время как (праведное) увеличение использования классов, таких как std::unique_ptr
в качестве членов класса, делало не скопируемые классы гораздо более распространенными животными, чем они были раньше.
Ответы
Ответ 1
user-declared
означает либо предоставленный пользователем (определенный пользователем), явно установленный по умолчанию (= default
), либо явно удаленный (= delete
), в отличие от неявно дефолтных/удаленных (например, вашего конструктора перемещения).
Таким образом, в вашем случае да, конструктор перемещения неявно удаляется, потому что экземпляр-конструктор явно удален (и, таким образом, объявлен пользователем).
Однако, в этом конкретном случае, как удалить конструктор/назначение конструктора/назначения беспорядка по умолчанию?
Это не так, но стандарт не делает разницы между этим случаем и сложным.
Самый короткий ответ заключается в том, что наличие неявно определенного конструктора move с явно удаленным конструктором-копированием может быть опасным в некоторых случаях, то же самое, когда у вас есть определяемый пользователем деструктор и не определенный пользователем конструктор-копир (см. Правило 3/пять/ноль). Теперь вы можете утверждать, что определяемый пользователем деструктор не удаляет конструктор-копию, но это просто недостаток на языке, который нельзя удалить, поскольку он сломает много старой (плохой) программы. Процитировать Bjarne Stroustrup:
В идеальном мире я думаю, что мы решили бы "нет поколения" в качестве дефолта и представить действительно простую нотацию "дать мне все обычные операции". [...] Кроме того, политика "без операций по умолчанию" приводит к ошибкам компиляции времени (которые мы должны иметь простой способ исправить), тогда как операция генерации по умолчанию приводит к проблемам, которые не могут быть обнаружены до времени выполнения.
Подробнее об этом можно узнать в N3174 = 10-0164.
Обратите внимание, что большинство людей следуют правилу три/пять/ноль, и, на мой взгляд, вы должны. Путем неявного удаления конструктора move по умолчанию стандарт "защищает" вас от ошибок и должен был защищать вас задолго до того, удалив в некоторых случаях экземпляр-копию (см. Статью Бьярне).
Дальнейшее чтение, если вы заинтересованы:
Я думаю, что этот вопрос имеет практическое значение, потому что ручная генерация и особенно. поддержание таких функций по умолчанию является подверженным ошибкам, в то время как (праведное) увеличение использования классов, таких как std::unique_ptr
как членов класса, делало не скопируемые классы гораздо более распространенными животными, чем они были раньше.
Пометка конструктора перемещения, как явно дефолт, решит эту проблему:
class Foo {
public:
Foo() = default;
Foo(Foo const &) = delete;
Foo(Foo&&) = default;
};
Вы получаете объект, не подлежащий копированию, с конструктором перемещения по умолчанию, и, на мой взгляд, эти явные объявления лучше, чем неявные (например, только объявляя конструктор перемещения по default
не удаляя конструктор-копию).
Ответ 2
Как вы сказали, из §12.8
Если определение класса X явно не объявляет конструктор перемещения, он будет объявлен неявным образом как по умолчанию, если и только если
Обратите внимание на объявленное пользователем. Но если вы посмотрите на §8.4.3:
Определение функции вида:
атрибут-specifier-seq opt decl-specifier-seq opt declarator virt-specifier-seq opt= delete;
называется удаленным определением. Функция с удаленным определением также называется удаленной функцией.
Программа, которая ссылается на удаленную функцию неявно или явно, кроме объявляет ее, плохо сформирована.
Таким образом, стандарт определяет delete
d функции как объявленные пользователем (как вы можете видеть выше), хотя они delete
d и не могут использоваться.
Затем, согласно § 12.8, конструктор неявного перемещения не определен, из-за объявленного пользователем (с = delete;
) конструктора копирования.
Ответ 3
Поведение может быть проиллюстрировано с помощью:
void foo(const int& i)
{
std::cout << "copy";
}
void foo(int&& i)
{
std::cout << "move";
}
При наличии обеих перегрузок перегрузка int&&
выбрана для значений r, тогда как перегрузка const int&
выбрана для lvalues. Если вы удалите перегрузку int&&
, const int&
вызывается (ergo, а не ошибка) даже для rvalues. По сути, это происходит в этом случае:
class Foo
{
public:
Foo() = default;
Foo(Foo const &) = delete;
};
Разрешение перегрузки видит только одного кандидата, но поскольку он явно удален, программа плохо сформирована.
Не нормативная заметка ниже раздела, который вы указали, разъясняет, что это происходит:
[Примечание. Если конструктор перемещения неявно не объявлен или явно указан, выражения, которые в противном случае вызывали бы move constructor может вместо этого ссылаться на конструктор копирования. - конечная нота ]