Правило Три становится Правилом Пяти с C++11?
Итак, после просмотра этой замечательной лекции о ссылках на rvalue я подумал, что каждый класс получит пользу от такого "конструктора перемещения", template<class T> MyClass(T&& other)
редактировать и, конечно, "оператор присваивания перемещения", template<class T> MyClass& operator=(T&& other)
Как указывает Филипп в своем ответе, если он имеет динамически распределенных членов или вообще хранит указатели. Точно так же, как у вас должны быть copy-ctor, оператор присваивания и деструктор, если применяются указанные выше пункты. Мысли?
9 ответов
Я бы сказал, что правило трех становится правилом трех, четырех и пяти:
Каждый класс должен явно определять точно один из следующего набора специальных функций-членов:
- Никто
- Деструктор, конструктор копирования, оператор назначения копирования
Кроме того, каждый класс, который явно определяет деструктор, может явно определять конструктор перемещения и / или оператор присваивания перемещения.
Обычно целесообразно использовать один из следующих наборов специальных функций-членов:
- Нет (для многих простых классов, где неявно генерируемые специальные функции-члены являются правильными и быстрыми)
- Деструктор, конструктор копирования, оператор присваивания копии (в этом случае класс не будет подвижным)
- Деструктор, конструктор перемещения, оператор присваивания перемещения (в этом случае класс не будет копируемым, что полезно для классов управления ресурсами, где базовый ресурс не подлежит копированию)
- Деструктор, конструктор копирования, оператор присваивания копии, конструктор перемещения (из-за исключения копирования нет издержек, если оператор присваивания копии получает свой аргумент по значению)
- Деструктор, конструктор копирования, оператор назначения копирования, конструктор перемещения, оператор назначения перемещения
Обратите внимание, что конструктор перемещения и оператор присваивания перемещения не будут созданы для класса, который явно объявляет любую из других специальных функций-членов, этот конструктор копирования и оператор присваивания копии не будут созданы для класса, который явно объявляет конструктор перемещения или перемещение оператор присваивания, и что класс с явно объявленным деструктором и неявно определенным конструктором копирования или неявно определенным оператором присвоения копии считается устаревшим. В частности, следующий совершенно правильный 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
использует конструктор перемещения и оператор присваивания перемещения, если он доступен, и это должно быть достаточно быстро.
Классы, которые не предназначены для управления ресурсами (т. Е. Без непустого деструктора) или полиморфизма подтипа (т. Е. Без виртуального деструктора), не должны объявлять ни одну из пяти специальных функций-членов; все они будут генерироваться автоматически и вести себя правильно и быстро.
Я не могу поверить, что никто не связан с этим.
В основном статья утверждает "Правило нуля". Мне неуместно цитировать всю статью, но я считаю, что это главное:
Классы с пользовательскими деструкторами, конструкторами копирования / перемещения или операторами назначения копирования / перемещения должны иметь дело исключительно с владением. Другие классы не должны иметь пользовательских деструкторов, конструкторов копирования / перемещения или операторов назначения копирования / перемещения.
Также этот бит ИМХО важен:
Общие классы "владение в пакете" включены в стандартную библиотеку:
std::unique_ptr
а такжеstd::shared_ptr
, Благодаря использованию пользовательских объектов удаления, оба были сделаны достаточно гибкими, чтобы управлять практически любым видом ресурса.
Я так не думаю, правило трех - это практическое правило, которое гласит, что класс, реализующий одно из следующих, но не все из них, вероятно, содержит ошибки.
- Копировать конструктор
- Оператор присваивания
- Destructor
Однако исключение конструктора перемещения или оператора присваивания перемещения не означает ошибку. Это может быть упущенная возможность при оптимизации (в большинстве случаев) или семантика перемещения не относится к этому классу, но это не ошибка.
Хотя может оказаться целесообразным определять конструктор перемещения, когда это уместно, это не обязательно. Во многих случаях конструктор перемещения не подходит для класса (например, std::complex
) и все классы, которые ведут себя правильно в C++03, будут продолжать работать правильно в C++0x, даже если они не определяют конструктор перемещения.
Да, я думаю, что было бы неплохо предоставить конструктор перемещения для таких классов, но помните, что:
Это всего лишь оптимизация.
Реализация только одного или двух из конструктора копирования, оператора присваивания или деструктора, вероятно, приведет к ошибкам, в то время как отсутствие конструктора перемещения только потенциально снизит производительность.
Конструктор Move не всегда может быть применен без изменений.
Некоторые классы всегда имеют свои указатели, и, таким образом, такие классы всегда удаляют свои указатели в деструкторе. В этих случаях вам нужно будет добавить дополнительные проверки, чтобы сказать, размещены ли их указатели или они были удалены (теперь являются нулевыми).
Вот краткое обновление о текущем состоянии и связанных с ним событиях с 24 января 2011 года.
В соответствии со стандартом C++11 (см. Приложение D [depr.impldec]):
Неявное объявление конструктора копирования считается устаревшим, если в классе есть объявленный пользователем оператор копирования или объявленный пользователем деструктор. Неявное объявление оператора присваивания копии считается устаревшим, если в классе имеется объявленный пользователем конструктор копирования или объявленный пользователем деструктор.
Было фактически предложено устареть устаревшее поведение, дав C++14 истинное "правило пяти" вместо традиционного "правила трех". В 2013 году EWG проголосовала против этого предложения, которое будет реализовано в C++2014. Основное обоснование решения по предложению было связано с общими опасениями по поводу нарушения существующего кода.
Недавно было вновь предложено адаптировать формулировку C++11 для достижения неформального правила пяти, а именно:
никакие функции копирования, перемещения или деструктора не могут быть сгенерированы компилятором, если любая из этих функций предоставлена пользователем.
В случае одобрения EWG, "правило" может быть принято для 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&&);
};
В приведенном выше примере нет необходимости объявлять какие-либо другие специальные функции как удаленные. Они просто не будут генерироваться из-за ограничительных правил. Наличие объявленных пользователем операций перемещения отключает генерируемые компилятором операции копирования. Но в таком случае:
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 становится правилом 4 (или 5) сейчас без нарушения всего существующего кода, который применяет правило 3 и не реализует какую-либо форму семантики перемещения.
Правило 3 означает, что если вы реализуете одно, вы должны реализовать все 3.
Также не известно, будет ли автоматически сгенерированный ход. Цель "правила 3" состоит в том, что они автоматически существуют, и если вы реализуете одно, то, скорее всего, реализация двух других по умолчанию неверна.
В общем случае, тогда да, правило трех стало просто пятью, с добавленным оператором перемещения и конструктором перемещения. Однако не все классы являются копируемыми и перемещаемыми, некоторые просто перемещаемыми, некоторые просто копируемыми.
Проще говоря, просто запомните это.
Правило 0 :
Classes have neither custom destructors, copy/move constructors or copy/move assignment operators.
Правило 3. Если вы реализуете индивидуальную версию любого из них, вы реализуете их все.
Destructor, Copy constructor, copy assignment
Правило 5. Если вы реализуете настраиваемый конструктор перемещения или оператор присваивания перемещения, вам необходимо определить все 5 из них. Требуется для семантики перемещения.
Destructor, Copy constructor, copy assignment, move constructor, move assignment
Правило четырех с половиной : То же, что Правило 5, но с идиомой копировать и менять местами. С включением метода подкачки присваивание копирования и присваивание перемещения объединяются в один оператор присваивания.
Destructor, Copy constructor, move constructor, assignment, swap (the half part)
Ссылки :
https://www.linkedin.com/learning/c-plus-plus-advanced-topics/rule-of-five?u=67551194https://en.cppreference.com/w/cpp/language/rule_of_three