Ответ 1
Конструкторы копирования и операторы присваивания очень важны в С++, потому что язык имеет "семантику копирования", то есть когда вы передаете параметр или сохраняете значение в контейнере, копия объекта передается или сохраняется. Как С++ может сделать копию или выполнить присвоение объекта? Для родных типов он знает сам по себе, но для пользовательских типов вместо этого он автоматически генерирует построение или назначение копии по отдельности.
Более подробно, если вы заявляете, например:
class P2d
{
public:
double x, y;
P2d(double x, double y) : x(x), y(y)
{ }
};
компилятор С++ автоматически завершает ваш код до
class P2d
{
public:
double x, y;
P2d(double x, double y) : x(x), y(y)
{ }
P2d(const P2d& other) : x(other.x), y(other.y)
{ }
P2d& operator=(const P2d& other)
{
x = other.x;
y = other.y;
return *this;
}
};
Являются ли эти автоматически создаваемые конструкторы экземпляров и операторы присваивания правильными для вашего класса? Во многих случаях да... но, конечно, возможно, эти реализации совершенно неправы. Довольно часто, например, когда у вас есть указатели, содержащиеся внутри ваших объектов, то просто копировать указатель, когда вы хотите сделать копию объекта, не так, как нужно.
Вы должны понимать, что С++ делает много копий объектов, и вы должны понять, какой тип копии он будет делать для определенных вами классов. Если автоматически созданная копия не то, что вам нужно, вы должны либо предоставить свою собственную реализацию, либо вы должны сообщить компилятору, что копирование должно быть запрещено для вашего класса.
Вы можете запретить компилятору создавать копии, объявляя конструктор и оператор присваивания private, а не предоставляя реализацию. Поскольку это частные функции, и любой внешний код, который будет их использовать, получит ошибку компилятора, и поскольку вы их объявили, но вы их не реализовали, вы получите сообщение об ошибке, если по ошибке вы в конечном итоге сделать копии внутри реализации класса.
Например:
class Window
{
public:
WindowType t;
Window *parent,
*prev_in_parent, *next_in_parent,
*first_children, *last_children;
Window(Window *parent, WindowType t);
~Window();
private:
// TABOO! - declared but not implemented
Window(const Window&); // = delete in C++11
Window& operator=(const Window&); // = delete in C++11
};
Если последняя часть кажется абсурдной (как вы можете сделать копии в реализации по ошибке), обратите внимание, что на С++ очень просто сделать лишние копии по ошибке, потому что язык построен вокруг концепции копирования вещей вокруг.
Золотое правило состоит в том, что если ваш класс имеет деструктор (потому что ему нужно выполнить некоторую очистку), то, скорее всего, копия по отдельности не подходит... а также если у вас есть специальная логика чтобы сделать построение копии, аналогичная логика, вероятно, необходима также при назначении (и наоборот). Таким образом, правило, известное как Большая тройка, указывает, что либо ваш класс не имеет настраиваемого деструктора, ни конструктора копирования, ни оператора присваивания, ни вашего класса должны иметь все три из них.
Это правило настолько важно, что, например, если для любого специального случая вы получаете класс, которому просто нужен деструктор (я не могу представить разумный случай... но позвольте сказать, что вы его нашли), тогда, пожалуйста, помните чтобы добавить в качестве комментария, который вы подумали об этом, и вы знаете, что неявно созданный конструктор копий и операторы присваивания в порядке. Если вы не добавите заметки о двух других, тот, кто прочитает ваш код, подумает, что вы просто забыли о них.