Правило трех становится правилом пяти с C++11?

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

8 ответов


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

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

  • нет
  • деструктор, конструктор копирования, оператор присваивания копий

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

обычно, один из следующих наборов специального члена функции разумно:

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

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

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
};

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

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

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

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


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

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

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

также этот бит IMHO важно:

общие классы "собственность в пакете" включены в стандарт библиотека: std::unique_ptr и std::shared_ptr. С помощью пользовательские объекты deleter, оба были сделаны достаточно гибкими для управления практически любой ресурс.


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

  1. конструктор копирования
  2. оператор присваивания
  3. деструктор

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

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


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

  • Это всего лишь оптимизация.

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

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

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


вот краткое обновление о текущем состоянии и связанных с ним событиях с 24 января '11.

в соответствии со стандартом C++11 (см. Приложение D [depr.impldec]):

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

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

в последнее время предложил снова адаптировать C++11 формулировка для достижения неофициального правила пяти, а именно, что

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

в случае одобрения РГЭ "правило", вероятно, будет принято для C++17.


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

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

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

вот некоторые примеры, которые должны быть в порядке с новейшими правилами C++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&&);
};

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

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

a Теперь ожидается, что компилятор C++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;
};

Итак, правило трех все еще применяется здесь просто из-за совместимости c++03.


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

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

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


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