Каково правило трех?

  • что значит копирование объекта В смысле?
  • что такое конструктор копирования и оператор присваивания копии?
  • когда мне нужно объявить их себе?
  • Как предотвратить копирование объектов?

8 ответов


введение

C++ обрабатывает переменные пользовательских типов с помощью значение семантики. Это означает, что объекты неявно копируются в различных контекстах, и мы должны понимать, что на самом деле означает" копирование объекта".

рассмотрим простой пример:

class person
{
    std::string name;
    int age;

public:

    person(const std::string& name, int age) : name(name), age(age)
    {
    }
};

int main()
{
    person a("Bjarne Stroustrup", 60);
    person b(a);   // What happens here?
    b = a;         // And here?
}

(если вы озадачены name(name), age(age) часть, это называется список инициализаторов членов.)

специальные членом функции

что значит копировать


на правило трех является эмпирическим правилом для C++, в основном говоря

Если ваш класс нуждается в какой-либо из

  • a конструктор копирования,
  • an оператор присваивания,
  • или деструктор,

определено явно, тогда, вероятно, потребуется все трое.

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

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

(обратите внимание, что предстоящая новая версия стандарта C++ (которая является C++11) добавляет move семантика на C++, что, скорее всего, изменит правило трех. Однако я слишком мало знаю об этом, чтобы писать раздел C++11 о правиле трех.)


закон большой тройки, как указано выше.

простой пример, на простом английском языке, типа проблемы, которую он решает:

деструктор не по умолчанию

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

вы можете подумать, что это работа.

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

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

поэтому вы пишете конструктор копирования, чтобы он выделял новым объектам собственные фрагменты памяти для уничтожения.

оператор присваивания и конструктор копирования

вы выделили память в конструкторе указатель-член класса. При копировании объекта данного класса оператор присваивания по умолчанию и конструктор копирования будет копировать значение этого указателя на новый объект.

Это означает, что новый объект и старый объект будут указывать на один и тот же фрагмент памяти, поэтому, когда вы измените его в одном объекте, он будет изменен и для другого objerct. Если один объект удаляет эту память, другой будет продолжать пытаться ее использовать - крон.

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


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

    MyClass x(a, b);
    MyClass y(c, d);
    x = y; // This is a shallow copy if assignment operator is not provided

Если MyClass имеет только некоторые примитивные типизированные члены, оператор присваивания по умолчанию будет работать, но если у него есть некоторые члены указателя и объекты, у которых нет операторов присваивания, результат будет непредсказуемым. Поэтому можно сказать, что если есть что-то чтобы удалить в деструкторе класса, нам может понадобиться оператор глубокой копии, что означает, что мы должны предоставить конструктор копирования и оператор присваивания.


что означает копирование объекта? Есть несколько способов копирования объектов-давайте поговорим о двух типах, которые вы, скорее всего, имеете в виду-глубокая копия и мелкая копия.

поскольку мы находимся на объектно-ориентированном языке (или, по крайней мере, предполагаем это), предположим, что у вас есть выделенная часть памяти. Поскольку это OO-язык, мы можем легко ссылаться на куски памяти, которые мы выделяем, потому что они обычно являются примитивными переменными (ints, chars, bytes) или классами, которые мы определили наших собственных типов и примитивов. Итак, допустим у нас есть класс автомобиля следующим образом:

class Car //A very simple class just to demonstrate what these definitions mean.
//It's pseudocode C++/Javaish, I assume strings do not need to be allocated.
{
private String sPrintColor;
private String sModel;
private String sMake;

public changePaint(String newColor)
{
   this.sPrintColor = newColor;
}

public Car(String model, String make, String color) //Constructor
{
   this.sPrintColor = color;
   this.sModel = model;
   this.sMake = make;
}

public ~Car() //Destructor
{
//Because we did not create any custom types, we aren't adding more code.
//Anytime your object goes out of scope / program collects garbage / etc. this guy gets called + all other related destructors.
//Since we did not use anything but strings, we have nothing additional to handle.
//The assumption is being made that the 3 strings will be handled by string's destructor and that it is being called automatically--if this were not the case you would need to do it here.
}

public Car(const Car &other) // Copy Constructor
{
   this.sPrintColor = other.sPrintColor;
   this.sModel = other.sModel;
   this.sMake = other.sMake;
}
public Car &operator =(const Car &other) // Assignment Operator
{
   if(this != &other)
   {
      this.sPrintColor = other.sPrintColor;
      this.sModel = other.sModel;
      this.sMake = other.sMake;
   }
   return *this;
}

}

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

Car car1 = new Car("mustang", "ford", "red");
Car car2 = car1; //Call the copy constructor
car2.changePaint("green");
//car2 is now green but car1 is still red.

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

//Shallow copy example
//Assume we're in C++ because it's standard behavior is to shallow copy objects if you do not have a constructor written for an operation.
//Now let's assume I do not have any code for the assignment or copy operations like I do above...with those now gone, C++ will use the default.

 Car car1 = new Car("ford", "mustang", "red"); 
 Car car2 = car1; 
 car2.changePaint("green");//car1 is also now green 
 delete car2;/*I get rid of my car which is also really your car...I told C++ to resolve 
 the address of where car2 exists and delete the memory...which is also
 the memory associated with your car.*/
 car1.changePaint("red");/*program will likely crash because this area is
 no longer allocated to the program.*/

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

что конструктор копирования и оператор копирующего присваивания? Я уже использовал их выше. Копия конструктор вызывается при вводе кода, например Car car2 = car1; по существу, если вы объявляете переменную и назначаете ее в одной строке, тогда вызывается конструктор копирования. Оператор присваивания-это то, что происходит, когда вы используете знак равенства--car2 = car1;. Уведомление car2 не объявлено в том же заявлении. Два фрагмента кода, которые вы пишете для этих операций, вероятно, очень похожи. На самом деле типичный шаблон дизайна имеет другую функцию, которую вы вызываете, чтобы установить все, как только вы удовлетворены первоначальное копирование / назначение является законным-если вы посмотрите на длинный код, который я написал, функции почти идентичны.

когда мне нужно объявить их себе? Если вы не пишете код, который должен быть общим или для производства каким-либо образом, вам действительно нужно только объявить их, когда они вам нужны. Вам нужно знать, что делает ваш язык программы, если вы решили использовать его "случайно" и не сделали его-т. е. вы получаете компилятор по умолчанию. Я редко использую copy например, конструкторы, но переопределения операторов присваивания очень распространены. Знаете ли вы, что вы можете переопределить то, что сложение, вычитание и т. д. имею в виду, а?

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


когда мне нужно объявить их себе?

правило трех гласит, что если вы объявите любой из

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

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

  • независимо от того, управление ресурсами было сделано в одной операции копирования, вероятно, необходимо было сделать в другой операции копирования и

  • деструктор класса также будет участвовать в управлении ресурсом (обычно освобождая его). Классическим ресурсом для управления была память, и именно поэтому все стандартные классы библиотек, которые управление памятью (например, контейнерами STL, выполняющими динамическую память управление) все объявляют "большую тройку": как операции копирования, так и деструктор.

следствие правила трех наличие объявленного пользователем деструктора указывает на то, что простая копия члена вряд ли будет подходящей для операций копирования в классе. Это, в свою очередь, предполагает, что если класс объявляет деструктор, операции копирования, вероятно, не должны создаваться автоматически, потому что они не будут делать правильно вещь. В то время, когда был принят C++98, значение этой линии рассуждений не было полностью оценено, поэтому в C++98 существование объявленного пользователем деструктора не повлияло на готовность компиляторов генерировать операции копирования. Это по-прежнему имеет место в C++11, но только потому, что ограничение условий, при которых создаются операции копирования, нарушит слишком много устаревшего кода.

как я могу предотвратить мои объекты от скопировано?

объявить конструктор копирования и оператор присваивания копий, как собственный спецификатор доступа.

class MemoryBlock
{
public:

//code here

private:
MemoryBlock(const MemoryBlock& other)
{
   cout<<"copy constructor"<<endl;
}

// Copy assignment operator.
MemoryBlock& operator=(const MemoryBlock& other)
{
 return *this;
}
};

int main()
{
   MemoryBlock a;
   MemoryBlock b(a);
}

В C++11 и далее вы также можете объявить конструктор копирования и оператор присваивания deleted

class MemoryBlock
{
public:
MemoryBlock(const MemoryBlock& other) = delete

// Copy assignment operator.
MemoryBlock& operator=(const MemoryBlock& other) =delete
};


int main()
{
   MemoryBlock a;
   MemoryBlock b(a);
}

многие из существующих ответов уже касаются конструктора копирования, оператора присваивания и деструктора. Однако в post c++11 введение Move semantic может расширить это за пределы 3.

недавно Михаил Клессе рассказал, что затрагивает эту тему: http://channel9.msdn.com/events/CPP/C-PP-Con-2014/The-Canonical-Class


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

конструктор копирования в C++ - это специальный конструктор. Он используется для создания нового объекта, который является новый объект, эквивалентный копии существующего объекта.

Copy assignment operator-специальный оператор присваивания, который обычно используется для указания существующего объекта другим объектам того же типа.

есть быстрые примеры:

// default constructor
My_Class a;

// copy constructor
My_Class b(a);

// copy constructor
My_Class c = a;

// copy assignment operator
b = a;