Правило 3 ( правило 5 в новом стандарте c ++) гласит:
Если вам нужно явно объявить деструктор, конструктор копирования или оператор копирования, вы, вероятно, должны явно объявить все три из них.
Но, с другой стороны, « чистый код » Мартина советует удалить все пустые конструкторы и деструкторы (стр. 293, G12: Clutter ):
Какой смысл использовать конструктор по умолчанию без реализации? Все, что ему нужно, - это засорять код бессмысленными артефактами.
Итак, как справиться с этими двумя противоположными мнениями? Должны ли быть реализованы пустые конструкторы / деструкторы?
Следующий пример демонстрирует именно то, что я имею в виду:
#include <iostream>
#include <memory>
struct A
{
A( const int value ) : v( new int( value ) ) {}
~A(){}
A( const A & other ) : v( new int( *other.v ) ) {}
A& operator=( const A & other )
{
v.reset( new int( *other.v ) );
return *this;
}
std::auto_ptr< int > v;
};
int main()
{
const A a( 55 );
std::cout<< "a value = " << *a.v << std::endl;
A b(a);
std::cout<< "b value = " << *b.v << std::endl;
const A c(11);
std::cout<< "c value = " << *c.v << std::endl;
b = c;
std::cout<< "b new value = " << *b.v << std::endl;
}
Компилируется нормально, используя g ++ 4.6.1 с:
g++ -std=c++0x -Wall -Wextra -pedantic example.cpp
Деструктор для struct A
пуст и не очень нужен. Итак, он должен быть там, или он должен быть удален?
источник
virtual ~base () = default;
не компилируется (по уважительной причине)auto_ptr
либо.Ответы:
Для начала правило говорит «вероятно», поэтому оно не всегда применимо.
Второй момент, который я вижу здесь, заключается в том, что если вам нужно объявить один из трех, это потому, что он делает что-то особенное, например, выделение памяти. В этом случае остальные не будут пустыми, поскольку им придется выполнять одну и ту же задачу (например, копирование содержимого динамически выделяемой памяти в конструкторе копирования или освобождение такой памяти).
Итак, в заключение, вы не должны объявлять пустые конструкторы или деструкторы, но очень вероятно, что если один нужен, то нужны и другие.
Что касается вашего примера: в таком случае вы можете не использовать деструктор. Это ничего не делает, очевидно. Использование умных указателей является прекрасным примером того, где и почему правило 3 не выполняется.
Это всего лишь руководство, где можно еще раз взглянуть на ваш код на случай, если вы забыли реализовать важные функции, которые иначе могли бы пропустить.
источник
Здесь действительно нет никакого противоречия. Правило 3 говорит о деструкторе, конструкторе копирования и операторе присваивания копии. Дядя Боб говорит о пустых конструкторах по умолчанию.
Если вам нужен деструктор, то ваш класс, вероятно, содержит указатели на динамически распределенную память, и вы, вероятно, хотите иметь ctor для копирования и a,
operator=()
которые делают глубокое копирование. Это полностью ортогонально тому, нужен ли вам конструктор по умолчанию.Также обратите внимание, что в C ++ существуют ситуации, когда вам нужен конструктор по умолчанию, даже если он пуст. Допустим, у вашего класса есть конструктор не по умолчанию. В этом случае компилятор не будет генерировать конструктор по умолчанию для вас. Это означает, что объекты этого класса не могут быть сохранены в контейнерах STL, потому что эти контейнеры ожидают, что объекты будут создаваться по умолчанию.
С другой стороны, если вы не планируете помещать объекты вашего класса в контейнеры STL, пустой конструктор по умолчанию, безусловно, бесполезный беспорядок.
источник
Здесь у вашего потенциального (*) эквивалента одному конструктору / присваиванию / деструктору по умолчанию есть цель: задокументировать тот факт, что у вас есть об этой проблеме, и определить, что поведение по умолчанию было правильным. Кстати, в C ++ 11 все еще недостаточно стабилизировалось, чтобы понять,
=default
может ли это послужить этой цели.(Существует еще одна потенциальная цель: предоставить внешнее определение вместо встроенного по умолчанию, лучше явное документирование, если у вас есть для этого основания).
(*) Потенциал, потому что я не помню случая из реальной жизни, когда правило трех не применимо, если я должен был что-то делать в одном, я должен был что-то делать в других.
Изменить после добавления примера. Ваш пример использования auto_ptr интересен. Вы используете умный указатель, но не тот, который подходит для работы. Я предпочел бы написать тот, который - особенно если ситуация возникает часто - чем делать то, что вы сделали. (Если я не ошибаюсь, ни стандарт, ни надстройка не обеспечивают).
источник
Правило 5 - это причинно-следственная связь с правилом 3, которое заключается в каутативном поведении и возможном неправильном использовании объекта.
Если вам нужен деструктор, это означает, что вы выполнили «управление ресурсами», отличное от значения по умолчанию (просто создавайте и уничтожайте значения ).
Так как копировать, назначать, перемещать и передавать по умолчанию значения копирования , если у вас нет только значений , вы должны определить, что делать.
Тем не менее, C ++ удаляет копию, если вы определяете движение, и удаляет движение, если вы определяете копию. В большинстве случаев вы должны определить, хотите ли вы эмулировать значение (следовательно, копировать mut, клонировать ресурс, и перемещать не имеет смысла) или менеджера ресурсов (и, следовательно, перемещать ресурс, где копирование не имеет смысла: правило из 3 становится правилом другого 3 )
Случаи, когда вам нужно определить и копировать, и переместить (правило 5), довольно редки: обычно у вас есть «большое значение», которое нужно скопировать, если оно дано отдельным объектам, но его можно переместить, если оно взято из временного объекта (избегая клон затем уничтожить ). Это касается контейнеров STL или арифметических контейнеров.
Случай может быть матрицами: они должны поддерживать копирование, потому что они являются значениями (
a=b; c=b; a*=2; b*=3;
не должны влиять друг на друга), но их можно оптимизировать, поддерживая также перемещение (a = 3*b+4*c
имеет значение,+
которое принимает два временных значения и генерирует временное: избежать клонирования и удаления можно полезно)источник
Я предпочитаю другую формулировку правила трех, которая кажется более разумной: «если вашему классу нужен деструктор (отличный от пустого виртуального деструктора), ему, вероятно, также понадобятся конструктор копирования и оператор присваивания».
Указание его как односторонних отношений от деструктора проясняет несколько вещей:
Он не применяется в тех случаях, когда вы предоставляете конструктор копирования не по умолчанию или оператор присваивания только в качестве оптимизации.
Причина этого правила в том, что конструктор копирования по умолчанию или оператор присваивания могут испортить ручное управление ресурсами. Если вы управляете ресурсами вручную, вы, вероятно, поняли, что вам нужен деструктор для их освобождения.
источник
Есть еще один момент, не упомянутый в обсуждении: деструктор всегда должен быть виртуальным.
Конструктор должен быть объявлен как виртуальный в базовом классе, чтобы сделать его виртуальным во всех производных классах. Таким образом, даже если вашему базовому классу не нужен деструктор, вы в конечном итоге объявляете и реализуете пустой деструктор.
Если вы включите все предупреждения (-Wall -Wextra -Weffc ++), g ++ предупредит вас об этом. Я считаю хорошей практикой всегда объявлять виртуальный деструктор в любом классе, потому что вы никогда не знаете, станет ли ваш класс базовым классом. Если виртуальный деструктор не нужен, он не причиняет вреда. Если это так, вы экономите время, чтобы найти ошибку.
источник