Правило Три становится Правилом Пяти с C ++ 11? - PullRequest
323 голосов
/ 24 января 2011

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

Ответы [ 8 ]

309 голосов
/ 24 января 2011

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

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

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

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

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

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

Обратите внимание, что конструктор перемещения и оператор присваивания перемещения не будут созданы для класса, который явно объявляет любую из других специальных функций-членов, конструктор копирования и оператор присваивания копирования не будутгенерируется для класса, который явно объявляет конструктор перемещения или оператор присваивания перемещения, и что класс с явно объявленным деструктором и неявно определенным конструктором копирования или неявно определенным оператором присваивания копии считается устаревшим.В частности, следующий совершенно правильный C ++ 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
};

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

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

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

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

67 голосов
/ 19 декабря 2012

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

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

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

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

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

18 голосов
/ 24 января 2011

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

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

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

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

14 голосов
/ 24 января 2011

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

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

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

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

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

8 голосов
/ 11 января 2015

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

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

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

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

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

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

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

4 голосов
/ 24 января 2011

По сути, это так: если вы не объявляете какие-либо операции перемещения, вы должны соблюдать правило трех.Если вы объявляете операцию перемещения, не будет никакого вреда в «нарушении» правила трех, поскольку генерация операций, сгенерированных компилятором, стала очень строгой.Даже если вы не объявляете операции перемещения и нарушаете правило трех, ожидается, что компилятор 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&&);
};

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

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

теперь ожидается, что компилятор 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 голосов
/ 24 января 2011

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

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

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

2 голосов
/ 24 января 2011

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

...