Правило-тройка становится Правилом пяти с С++ 11?

Итак, после просмотра этой замечательной лекции на ссылках rvalue, я подумал, что каждый класс выиграет от такого "конструктора перемещения", template<class T> MyClass(T&& other) отредактируйте и, конечно, "оператор назначения перемещения", template<class T> MyClass& operator=(T&& other), как указывает Филипп в своем ответе, если он имеет динамически распределенные элементы или обычно хранит указатели. Так же, как вы должны иметь copy-ctor, оператор присваивания и деструктор, если применяются упомянутые выше пункты. Мысли?

Ответ 1

Я бы сказал, что Правило Трех становится Правилом Трех, Четырех и Пяти:

Каждый класс должен явно определить точно один из следующих наборов специальных функций-членов:

  • Никто
  • Деструктор, конструктор копирования, оператор присваивания копии

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

Обычно один из следующих наборов специальных функций-членов является разумным:

  • None (для многих простых классов, где неявно созданные специальные функции-члены правильные и быстрые)
  • Деструктор, конструктор копирования, оператор присваивания копии (в этом случае класс не будет перемещаться)
  • Destructor, move constructor, move assign operator (в этом случае класс не будет скопирован, полезен для классов управления ресурсами, где базовый ресурс не копируется)
  • Деструктор, конструктор копирования, оператор присваивания копии, конструктор перемещения (из-за копирования, нет накладных расходов, если оператор присваивания копии принимает свой аргумент по значению)
  • Деструктор, конструктор копирования, оператор присваивания копии, оператор перемещения, оператор переадресации

Обратите внимание, что оператор перемещения и оператор назначения перемещения не будут сгенерированы для класса, который явно объявляет любую из других специальных функций-членов, что оператор-конструктор копирования и оператор присваивания копии не будут сгенерированы для класса, который явно объявляет конструктор перемещения или перемещает оператор присваивания и что класс с явно объявленным деструктором и неявно определенным конструктором копирования или неявно определенным оператором присваивания копии считается устаревшим. В частности, следующий вполне корректный полиморфный базовый класс С++ 03

class C {
  virtual ~C() { }   // allow subtype polymorphism
};

следует переписать следующим образом:

class C {
  C(const C&) = default;               // Copy constructor
  C(C&&) = default;                    // Move constructor
  C& operator=(const C&) = default;  // Copy assignment operator
  C& operator=(C&&) = default;       // Move assignment operator
  virtual ~C() { }                     // Destructor
};

Немного раздражает, но, вероятно, лучше, чем альтернатива (автоматическое создание всех специальных функций-членов).

В отличие от правила Большой тройки, где несоблюдение правила может нанести серьезный ущерб, а явно не объявлять конструктор перемещения и оператор переадресации, как правило, хороши, но часто субоптимальны по эффективности. Как упоминалось выше, оператор перемещения конструктора и перемещения присваивается только при отсутствии явно объявленного конструктора копирования, оператора назначения копирования или деструктора. Это не является симметричным для традиционного поведения С++ 03 в отношении автогенерации конструктора копирования и оператора присваивания копий, но гораздо безопаснее. Таким образом, возможность определения конструкторов перемещения и операторов назначения перемещения очень полезна и создает новые возможности (чисто подвижные классы), но классы, которые придерживаются правила С++ 03 Большой тройки, все равно будут в порядке.

Для классов управления ресурсами вы можете определить конструктор копирования и оператор назначения копирования как удаленный (который считается определением), если базовый ресурс не может быть скопирован. Часто вам по-прежнему нужен механизм перемещения и перемещение оператора присваивания. Копирование и перемещение операторов присваивания часто реализуются с помощью swap, как в С++ 03. Если у вас есть конструктор перемещения и оператор std::swap, специализация std::swap станет несущественной, поскольку общий std::swap использует конструктор перемещения и перемещает оператор присваивания, если он доступен, и это должно быть достаточно быстро.

Классы, которые не предназначены для управления ресурсами (т.е. Непустого деструктора) или полиморфизма подтипов (т.е. Виртуального деструктора), не должны объявлять ни одну из пяти специальных функций-членов; все они будут автоматически генерироваться и вести себя правильно и быстро.

Ответ 2

Я не могу поверить, что с этим никто не связан.

В основном статья утверждает "Правило нуля". Мне не подходит приводить целую статью, но я считаю, что это главное:

Классы, которые имеют настраиваемые деструкторы, копировать/перемещать конструкторы или копировать/перемещать операторы присваивания, должны касаться исключительно владения. Другие классы не должны иметь настраиваемых деструкторов, копировать/перемещать конструкторы или копировать/перемещать операторы присваивания.

Также этот бит ИМХО важен:

В стандартную библиотеку входят стандартные классы "собственность в одном пакете": std::unique_ptr и std::shared_ptr. Благодаря использованию пользовательских объектов-дебетов, оба из них были достаточно гибкими, чтобы управлять практически любым видом ресурсов.

Ответ 3

Я так не думаю, правило из трех - это правило, которое гласит, что класс, который реализует одно из следующих, но не все они, вероятно, ошибочны.

  • Конструктор копирования
  • Оператор присваивания
  • Destructor

Однако, если оставить конструктор перемещения или переместить оператор присваивания, это не означает ошибку. Это может быть упущенная возможность при оптимизации (в большинстве случаев) или что семантика перемещения не относится к этому классу, но это не ошибка.

Хотя целесообразно определить конструктор перемещения, если это необходимо, это не обязательно. Существует много случаев, когда конструктор перемещения не относится к классу (например, std::complex), и все классы, которые корректно ведут себя на С++ 03, будут продолжать корректно вести себя в С++ 0x, даже если они не определяют конструктор перемещения.

Ответ 4

Да, я думаю, было бы неплохо предоставить конструктор перемещения для таких классов, но помните, что:

  • Это только оптимизация.

    Реализация только одного или двух из конструктора копирования, оператора присваивания или деструктора, вероятно, приведет к ошибкам, в то время как отсутствие конструктора перемещения просто потенциально снизит производительность.

  • Конструктор перемещения не всегда может быть применен без изменений.

    В некоторых классах всегда выделяются указатели, и поэтому такие классы всегда удаляют свои указатели в деструкторе. В этих случаях вам нужно будет добавить дополнительные проверки, чтобы сказать, выделены ли их указатели или были удалены (теперь они равны нулю).

Ответ 5

Вот краткое обновление текущего состояния и связанных с ним событий с 24 января 2011 года.

Согласно стандарту С++ 11 (см. Приложение D [des.impldec]):

Неявное объявление конструктора копии устарело, если класс имеет пользовательский оператор назначения копирования или объявленный пользователем деструктор. Неявное объявление оператора присваивания копии устарело, если класс имеет объявленный пользователем конструктор копирования или объявленный пользователем деструктор.

Фактически предложил отказаться от устаревшего поведения, дающего С++ 14 истинное "правило из пяти" вместо традиционного "правила из трех". В 2013 году EWG проголосовала против этого предложения, которое должно быть реализовано на С++ 2014. Основное обоснование решения по этому предложению было связано с общей озабоченностью по поводу нарушения существующего кодекса.

Недавно было предложено адаптировать формулировку С++ 11 для достижения неформального правила пяти, а именно:

функция копирования, функция перемещения или деструктор не генерируются компилятором, если любая из этих функций предоставляется пользователю.

Если это одобрено EWG, "правило", скорее всего, будет принято для С++ 17.

Ответ 6

В принципе, это так: если вы не объявляете никаких операций перемещения, вы должны соблюдать правило три. Если вы объявите операцию перемещения, нет никакого вреда в "нарушении" правила из трех, поскольку генерация генерируемых компилятором операций стала очень ограничительной. Даже если вы не объявляете операции перемещения и не нарушаете правило три, ожидается, что компилятор С++ 0x даст вам предупреждение в случае, если одна специальная функция была объявлена ​​пользователем, а другие специальные функции были автоматически сгенерированы из-за теперь устарело "С++ 03 совместимость правила".

Думаю, можно с уверенностью сказать, что это правило становится немного менее значительным. Реальная проблема в С++ 03 заключается в том, что для реализации различной семантики копии вам необходимо было объявить все связанные специальные функции, чтобы ни одна из них не была сгенерирована компилятором (что в противном случае было бы неправильным). Но С++ 0x изменяет правила создания специальной функции-члена. Если пользователь объявляет только одну из этих функций, чтобы изменить семантику копирования, это не позволит компилятору автоматически генерировать оставшиеся специальные функции. Это хорошо, потому что отсутствующее объявление превращает ошибку времени выполнения в ошибку компиляции (или, по крайней мере, предупреждение). В качестве меры совместимости С++ 03 некоторые операции все еще сгенерированы, но это поколение считается устаревшим и должно, по крайней мере, вызывать предупреждение в режиме С++ 0x.

Из-за довольно ограничительных правил о специальных функциях, созданных компилятором и совместимости с С++ 03, правило три остается правилом три.

Вот некоторые exaples, которые должны быть в порядке с новейшими правилами С++ 0x:

template<class T>
class unique_ptr
{
   T* ptr;
public:
   explicit unique_ptr(T* p=0) : ptr(p) {}
   ~unique_ptr();
   unique_ptr(unique_ptr&&);
   unique_ptr& operator=(unique_ptr&&);
};

В приведенном выше примере нет необходимости объявлять какие-либо из других специальных функций как удаленные. Они просто не будут генерироваться из-за ограничительных правил. Присутствие объявленных пользователем операций перемещения отключает операции копирования, генерируемые компилятором. Но в таком случае:

template<class T>
class scoped_ptr
{
   T* ptr;
public:
   explicit scoped_ptr(T* p=0) : ptr(p) {}
   ~scoped_ptr();
};

Теперь ожидается, что компилятор С++ 0x должен выпустить предупреждение о возможностях копирования, сгенерированных компилятором, которые могут сделать неправильную вещь. Здесь правило трех вопросов и должно соблюдаться. Предупреждение в этом случае является полностью подходящим и дает пользователю возможность справиться с ошибкой. Мы можем избавиться от проблемы с помощью удаленных функций:

template<class T>
class scoped_ptr
{
   T* ptr;
public:
   explicit scoped_ptr(T* p=0) : ptr(p) {}
   ~scoped_ptr();
   scoped_ptr(scoped_ptr const&) = delete;
   scoped_ptr& operator=(scoped_ptr const&) = delete;
};

Итак, правило три по-прежнему применяется здесь просто из-за совместимости с С++ 03.

Ответ 7

Мы не можем сказать, что правило 3 становится теперь правилом 4 (или 5) без нарушения всего существующего кода, который обеспечивает соблюдение правила 3 ​​и не реализует никакой формы семантики перемещения.

Правило 3 означает, что если вы его реализуете, вы должны реализовать все 3.

Также не известно, что будет выполнен автоматический сгенерированный ход. Цель "правила 3" заключается в том, что они автоматически существуют и, если вы их реализуете, скорее всего, реализация по умолчанию двух других неверна.

Ответ 8

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