Реализация конструктора копирования
Конструктор копирования вызывается, когда один объект создается через другой объект. Когда динамически распределяется пространство для членов класса, конструктор копирования ведет себя неадекватно, некорректно.
Пусть имеется 2 объекта типа Message, где один является копией другого.
Class Message
{private:
Char* pmessage;
Public:
Void ShowIt(){cout<<endl<<pmessage;}
Message(const char* text=”default message”)
{pmessage=new char(strlen(text)+1);
Strcpy(pmessage,text);}
~Message();
};
Messge::~Message()
{ cout<<”destructor called\n”;
Delete [] pmessage;}
Message mess1 (“visual studio 2010”);
Message mess2 (mess1);
Так как в классе Message не создан конструктор копирования, то для создания объекта Mess2, компилятор генерирует собственную версию конструктора копирования по умолчанию.
Как известно, эффект от конструктора копирования по умолчанию состоит в том, что копируется адрес, хранившийся в указателе-члене класса из mess1 в mess2. Это означает, что процесс копирования, реализованный конструктором копирования по умолчанию включает простое копирование значений, хранящихся в данных членах исходного объекта в новый объект. Следовательно, в нашем примере одна текстовая строка будет совместно использована двумя объектами.
Если строка в одном из двух объектов изменяется, она также изменяется и для другого, потому что оба объекта разделяют одну и ту же строку. Если mess1 уничтожается, то указатель в объекте mess2 указывает на область памяти, которая была уже освобождена и где-то уже используется и при попытке освободить память второго объекта компилятором, программа завершается аварийно.
Решение данной проблемы заключается в создании конструктора копирования, который заменит версию поумолчанию.
Пример:
Message (copy Message &initM) // ссылка необходима во избежание цикла
{pmessage=new char [strlen(initM.pmessage)+1];
Strcpy(pmessage, initM.pmessage);}
Для того, чтобы избежать бесконечного цикла конструкторов копирования, параметр должен быть специализирован как const_ссылка. Конструктор копирования сначала выделяет достаточную память, чтобы уместилась строка из объекта initM, сохраняя адрес в данном члене pmessage нового объекта, а затем, копирует текстовую строку из исходного объекта.
Второй случай, когда нужно создать конструктор копирования – передача в функцию в качестве аргумента объекта по значению, при этом данные объекты являются динамическими.
Void DisplayMessage(Message localMsg)
{cout<<endl<<”the message is ->”<<localMsg.ShowIt()<<endl;}
Message mess3(“I like programming in C++”);
DisplayMessage (mess3);
Функция DisplayMessage будет работать некорректно, т.к. параметр, который она принимает, является объектом и передается он по значению. При конструкторе копирования по умолчанию происходят следующие события:
1) Объект mess3 создается с местом для сообщения “I like programming in C++” с выделением динамической памяти.
2) Функция DisplayMessage вызывается и, поскольку аргумент передан по значению, копия аргумента localMsg создается конструктором копирования по умолчанию. И теперь указатель pmessage в копии указывает на ту же строку в свободном хранилище, что и в оригинальном объекте.
3) При завершении своей работы в функции DisplayMessage, объект выходит из области видимости и поэтому вызывается деструктор класса Message, который удаляет локальный объект, освобождая память, на которую указывает указатель pmessage.
4) При возврате из функции DisplayMessage, указатель на исходный объект, то есть mess3 все еще указывает на область памяти, которая уже была освобождена и в следующий раз, когда мы попытаемся использовать исходный объект, программа начинает себя вести странным образом. И даже если мы больше не используем исходный объект, то рано или поздно программа должна завершить свою работу, объект должен выходить из области видимости и удаляться деструктором. Поэтому рано или поздно программа должна закончиться аварийно.
Таким образом любой вызов функции, которая принимает по значению объект класса, имеющего динамический определенный член, приведет к проблемам. Отсюда возникает следующее правило: если имеется динамическое выделение памяти для члена класса, всегда следует реализовать конструктор копирования.
Перегрузка операции