C++: конструктор копирования, оператор присваивания, деструктор
Дата и время: 23 марта 2015 г. 19:27 | Категория: Программирование, C++
Конструктор копирования, деструктор и перегруженный оператор присваивания - незаменимые элементы каждого класса, работающего с динамически выделенной памятью.
1. Конструктор копирования
Конструктор копирования, в отличии от других, в качестве параметра принимает константную ссылку на объект класса.
// Прототип конструктора копирования
Klass(const Klass &);
Данный конструктор вызывается всякий раз, когда создаётся новый объект и для его инициализации берётся значение существующего объекта того же типа. Например, в следующих случаях:
Klass k2(k1);
Klass k3 = k2;
Klass k4 = Klass(k3);
Klass * pKlass = new Klass(k4);
Также конструктор копирования вызывается при передаче объекта в функцию или возврате из неё по значению. Аналогично, с помощью конструктора копирования создаются временные объекты при вычислении арифметических и других операций.
В чём же проблема отсутствия конструктора копирования при выделении в классе динамической памяти? Дело в том, что при отсутствии явного описания, он описывается неявно. Неявный конструктор выполняет поверхностное копирование, т. е. просто дублирует биты из переменных. Таким образом, вместо данных из динамической памяти, копируется адреса на них. В результате, появляется несколько объектов, указывающих на одну область памяти. При изменении этой области через один объект, она также изменится и в другом, что в большинстве случаев является нежелательным поведением. Поэтому в классах, работающих с динамической памятью, необходимо всегда явно объявлять конструктор копирования (см. пример в конце). Как вариант исключения данной проблемы, можно поместить конструктор копирования в приватной области класса, что вовсе запретит выполнять копирование.
2. Перегруженная операция присваивания
Перегруженная операция присваивания используется при присваивании одного объекта другому существующему объекту. Здесь присутствует такая же проблема, что и в конструкторе копирования. К тому же, у объекта, которому присваивается значение, уже может быть выделена динамическая память. Перед присваиванием новых данных, выделенную ранее память необходимо очистить, чтобы не допустить её утечки (см. пример в конце). Также необходимо обработать случай самоприсваивания. В противном случае, данные в динамической памяти просто будут утеряны. Аналогично копированию, присваивание также можно запретить, поместив операцию в приватной области класса.
3. Деструктор
Деструктор вызывается перед удалением объекта и предназначен для освобождения всех используемых ресурсов. Чтобы не допустить утечки памяти, в деструкторе необходимо её очистить.
4. Пример
#include <iostream>
struct Klass {
int size;
double * data;
Klass() {
std::cout << "Конструктор по умолчанию" << std::endl;
size = 100;
data = new double[size];
}
Klass(const Klass & klass) {
std::cout << "Конструктор копирования" << std::endl;
size = klass.size;
data = new double[size];
for (int i = 0; i < size; i++) {
data[i] = klass.data[i];
}
}
Klass & operator=(const Klass & klass) {
if (this != &klass) {
std::cout << "Перегруженный оператор присваивания" << std::endl;
delete[] data;
size = klass.size;
data = new double[size];
for (int i = 0; i < size; i++) {
data[i] = klass.data[i];
}
}
else {
std::cout << "Самоприсваивание" << std::endl;
}
return *this;
}
~Klass() {
std::cout << "Деструктор" << std::endl;
delete[] data;
}
};
int main() {
setlocale(LC_ALL, "Russian");
Klass k1;
Klass k2(k1);
Klass k3 = k2;
Klass k4 = Klass(k3);
Klass * pKlass = new Klass(k4);
k1 = k2;
k2 = k2;
}
Стоить отметить, что во всех трёх функциях память должна выделяться и удаляться одинаковым образом. Т. е. нельзя в одном случае использовать delete, а в другом delete[].
comments powered by Disqus