Конструктор/назначение конструктора по умолчанию и удаленный конструктор/назначение копии

Согласно стандарту,

Если определение класса 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 явно не объявляет конструктор перемещения, он будет объявлен неявным образом как по умолчанию, если и только если

  • У 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 может вместо этого ссылаться на конструктор копирования. - конечная нота ]