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)часть, это называется список инициализаторов членов.)специальные членом функции
что значит копировать a
на правило трех - Это правило для C++, в основном говорят
Если ваш класс нуждается в какой-либо из
- a конструктор копирования,
- an оператор присваивания,
- или деструктор,
определяется явно, то это, вероятно, нужно все трое.
причины этого в том, что все три из них обычно используются для управления ресурсом, и если ваш класс управляет ресурсом, ему обычно нужно управлять копированием, а также освобождением.
если нет хорошей семантики для копирования ресурса, которым управляет ваш класс, то подумайте о том, чтобы запретить копирование, объявив (не определение) конструктор копирования и оператор присваивания, как
private.(обратите внимание, что предстоящая новая версия стандарта C++ (которая является C++11) добавляет move семантика на C++, которая, скорее всего, изменит правило трех. Впрочем, я слишком мало знаю об этом написать раздел C++11 о правиле трех.)
закон большой тройки, как указано выше.
простой пример, на простом английском языке, о том, какую проблему он решает:
не деструктор по умолчанию
вы выделили память в конструкторе и так вам нужно написать деструктор, чтобы удалить его. В противном случае вы вызовете утечку памяти.
вы можете подумать, что это работа сделана.
проблема будет, если копия вашего объекта, то копия будет указывать на ту же память, что и исходный объект.
после того, как один из них удаляет память в своем деструкторе, другой будет иметь указатель на недопустимую память (это называется висячим указателем), когда он пытается использовать ее, все станет волосатым.
таким образом, вы пишете конструктор копирования так, что он выделяет новые объекты свои собственные части памяти для уничтожения.
оператор присваивания и конструктор копирования
вы выделили память в конструкторе указатель-член класса. При копировании объекта данного класса оператор присваивания по умолчанию и конструктор копирования будет копировать значение этого указателя на новый объект.
Это означает, что новый объект и старый объект будут указывать на один и тот же фрагмент памяти, поэтому при изменении его в одном объекте он также будет изменен для другого объекта. Если один объект удаляет эту память другой будет продолжать пытаться использовать его - крон.
чтобы решить эту проблему, вы пишете свою собственную версию конструктора копирования и оператора присваивания. Ваши версии выделяют отдельную память для новых объектов и копируют значения, на которые указывает первый указатель, а не его адрес.
в принципе, если у вас есть деструктор (не деструктор по умолчанию), это означает, что класс, который вы определили некоторые выделения памяти. Предположим, что класс используется вне каким-либо клиентским кодом или вами.
MyClass x(a, b); MyClass y(c, d); x = y; // This is a shallow copy if assignment operator is not providedЕсли MyClass имеет только некоторые примитивные типизированные члены, оператор присваивания по умолчанию будет работать, но если у него есть некоторые члены указателя и объекты, которые не имеют операторов присваивания, результат будет непредсказуемым. Поэтому можно сказать, что если есть что-то чтобы удалить в деструкторе класса, нам может понадобиться оператор глубокого копирования, что означает, что мы должны предоставить конструктор копирования и оператор присваивания.
что означает копирование объекта? Есть несколько способов копирования объектов -- давайте поговорим о 2 видах, которые вы, скорее всего, имеете в виду--глубокое копирование и мелкое копирование.
поскольку мы находимся в объектно-ориентированном языке (или, по крайней мере, предполагаем это), предположим, что у вас выделен кусок памяти. Поскольку это 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не объявляется в том же заявлении. Два фрагмента кода, которые вы пишете для этих операций, вероятно, очень похожи. На самом деле типичный шаблон дизайна имеет еще одну функцию, которую вы вызываете, чтобы установить все, как только вы удовлетворены первоначальное копирование / назначение является законным-если вы посмотрите на длинный код, который я написал, функции почти идентичны.когда я должен объявить их сам? Если вы не пишете код, который должен быть общим или для производства каким-либо образом, вам действительно нужно только объявить их, когда они вам нужны. Вам нужно знать, что делает ваш программный язык, если вы решили использовать его "случайно" и не сделали его, т. е. вы получаете компилятор по умолчанию. Я редко использую копию например, конструкторы, но переопределения операторов присваивания очень распространены. Знаете ли вы, что вы можете переопределить сложение, вычитание и т. д. значит, тоже?
как я могу предотвратить копирование моих объектов? Переопределение всех способов выделения памяти для объекта с помощью частной функции является разумным началом. Если вы действительно не хотите, чтобы люди копировали их, вы можете сделать его общедоступным и предупредить программиста, бросив исключение, а также не копируя объект.
когда я должен объявить их сам?
правило трех состояний, что если вы объявите любой из
- конструктор копирования
- оператор присваивания копии
- деструктор
- тогда вы должны объявить все три. Он вырос из наблюдения, что необходимость взять на себя значение операции копирования почти всегда вытекает из класса, выполняющего какой-то вид управления ресурсами, и что почти всегда подразумевалось, что
независимо от того, что управление ресурсами выполнялось в одной операции копирования, вероятно, необходимо было сделать в другой операции копирования и
деструктор класса также будет участвовать в управлении ресурсом (обычно освобождая его). Классическим ресурсом для управления была память, и именно поэтому все стандартные библиотечные классы, которые управление памятью (например, контейнеры 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 введение семантики перемещения может расширить это за пределы 3.
недавно Михаил Клессе рассказал, что затрагивает эту тему: http://channel9.msdn.com/events/CPP/C-PP-Con-2014/The-Canonical-Class
правило трех в C++ является фундаментальным принципом проектирования и разработки трех требований, что если есть четкое определение в одной из следующих функций-членов, то программист должен определить две другие функции-члены вместе. А именно, необходимы следующие три функции-члена: деструктор, конструктор копирования, оператор присваивания копирования.
конструктор копирования в C++ - это специальный конструктор. Он используется для построения нового объекта, который является новый объект эквивалентен копии существующего объекта.
оператор присваивания копирования-это специальный оператор присваивания, который обычно используется для указания существующего объекта другим объектам того же типа.
есть быстрые примеры:
// default constructor My_Class a; // copy constructor My_Class b(a); // copy constructor My_Class c = a; // copy assignment operator b = a;
Comments