Каково правило четырех (с половиной)?
для правильной обработки копирования объектов эмпирическим правилом является правило трех. С C++11 семантика перемещения-это вещь, поэтому вместо этого это правила пяти. Однако, в дискуссиях вокруг здесь и в Интернете, я также видел ссылки на правило четырех (с половиной), который представляет собой комбинацию правила пяти и идиомы копирования и замены.
Так что же такое правило четырех (с половиной)? Который функции должны быть реализованы, и как должно выглядеть тело каждой функции? Какая функция является половиной? Существуют ли какие-либо недостатки или предупреждения для этого подхода по сравнению с правилом пяти?
вот ссылочная реализация, которая напоминает мой текущий код. Если это неверно, как будет выглядеть правильная реализация?
//I understand that in this example, I could just use `std::unique_ptr`.
//Just assume it's a more complex resource.
#include <utility>
class Foo {
public:
//We must have a default constructor so we can swap during copy construction.
//It need not be useful, but it should be swappable and deconstructable.
//It can be private, if it's not truly a valid state for the object.
Foo() : resource(nullptr) {}
//Normal constructor, acquire resource
Foo(int value) : resource(new int(value)) {}
//Copy constructor
Foo(Foo const& other) {
//Copy the resource here.
resource = new int(*other.resource);
}
//Move constructor
//Delegates to default constructor to put us in safe state.
Foo(Foo&& other) : Foo() {
swap(other);
}
//Assignment
Foo& operator=(Foo other) {
swap(other);
return *this;
}
//Destructor
~Foo() {
//Free the resource here.
//We must handle the default state that can appear from the copy ctor.
//(The if is not technically needed here. `delete nullptr` is safe.)
if (resource != nullptr) delete resource;
}
//Swap
void swap(Foo& other) {
using std::swap;
//Swap the resource between instances here.
swap(resource, other.resource);
}
//Swap for ADL
friend void swap(Foo& left, Foo& right) {
left.swap(right);
}
private:
int* resource;
};
2 ответов
так что же такое правило четырех (с половиной)?
"правило" большой четверки " (с половиной)" гласит, что если вы реализуете один из
- копирующий конструктор
- оператор присваивания
- переместить конструктор
- деструктор
- функция подкачки
тогда у вас должна быть политика в отношении других.
какие функции должны реализовано, и как должно выглядеть тело каждой функции?
- конструктор по умолчанию (который может быть частным)
- конструктор копирования (здесь у вас есть реальный код для обработки вашего ресурса)
-
переместить конструктор (используя конструктор по умолчанию и swap):
S(S&& s) : S{} { swap(*this, s); }
-
оператор присваивания (с помощью конструктора и swap)
S& operator=(S s) { swap(*this, s); }
деструктор (глубокая копия вашего ресурс)
- friend swap (не имеет реализации по умолчанию :/ вы, вероятно, захотите поменять каждого члена). Это важно вопреки методу swap-члена:
std::swap
использует конструктор перемещения (или копирования), что приведет к бесконечной рекурсии.
функции половинку?
из предыдущей статьи:
" для реализации идиомы Copy-Swap ваш класс управления ресурсами также должен реализуйте функцию swap () для выполнения обмена по членам (есть ваш "...(полтора)")"
так swap
метод.
есть ли какие-либо недостатки или предупреждения для этого подхода, по сравнению с правилом пяти?
предупреждение, которое я уже написал, собирается написать правильный своп, чтобы избежать бесконечной рекурсии.
есть ли какие-либо недостатки или предупреждения для этого подхода, по сравнению с правилом пяти?
хотя он может сохранить дублирование кода, использование copy-and-swap просто приводит к худшим классам, чтобы быть тупым. Вы вредите производительности своего класса, включая назначение перемещения (если вы используете оператор унифицированного назначения, который я также не поклонник), который должен быть очень быстрым. Взамен вы получаете сильную гарантию исключения, которая сначала кажется приятной. Этот дело в том, что вы можете получить сильную гарантию исключения из любого класса с простой универсальный функция:
template <class T>
void copy_and_swap(T& target, T source) {
using std::swap;
swap(target, std::move(source));
}
и это все. Так люди которым нужна сильная безопасность исключения могут получить ее так или иначе. И, честно говоря, сильная безопасность исключений-это довольно ниша.
реальный способ сохранить дублирование кода - через правило нуля: выберите переменные-члены, чтобы вам не нужно было писать любой специальные функции. В реальной жизни, я бы сказал, что 90+ % от когда я вижу специальные функции-члены, их легко можно было избежать. Даже если ваш класс действительно имеет какую-то специальную логику, необходимую для специальной функции-члена, вам обычно лучше нажать ее вниз в член. Ваш класс logger может потребоваться очистить буфер в своем деструкторе, но это не причина писать деструктор: напишите небольшой класс буфера, который обрабатывает промывку и имеет это как член вашего регистратора. Лесозаготовители потенциально имеют все виды другие ресурсы, которые могут обрабатываться автоматически, и вы хотите, чтобы компилятор автоматически создавал код копирования/перемещения / уничтожения.
дело в том, что автоматическая генерация специальных функций-это все или ничего, для каждой функции. То есть конструктор копирования (например) либо генерируется автоматически, с учетом все члены, или вы должны написать (и хуже того, поддерживать) его все вручную. Так что это сильно подталкивает вас к подходу давить вниз.
в случаях, когда вы пишете класс для управления ресурсом и должны иметь дело с этим, он обычно должен быть: a) относительно небольшим и b) относительно общим/многоразовым. Первое означает, что немного дублированного кода не имеет большого значения, а последнее означает, что вы, вероятно, не хотите оставлять производительность на столе.
в сумме я настоятельно рекомендую использовать copy и swap, а также использовать унифицированные операторы присваивания. Попробуйте следовать правилу Зеро, если не можешь, следуй правилу пяти. Пиши swap
только если вы можете сделать это быстрее, чем общий своп (который делает 3 хода), но обычно вам не нужно беспокоиться.