Глава 12. Классы и динамическое выделение памяти
При почленном копировании копируется указатель, а не данные, что ведёт к двойному освобождению (конструктор Text() создаёт объект, деструктор ~Text() уничтожает объект). Показано на картинке.
#include <iostream>
#include <cstring>
class Text {
private:
char * data;
int length;
public:
Text(const char * str) {
length = std::strlen(str);
data = new char[length + 1]; //динамическая память new
std::strcpy(data, str);
}
~Text() {
delete[] data;
}
};
int main() {
Text t1("Hello");
Text t2 = t1;
return 0;
}
Здесь возникнет ошибка – двойное удаление, так как указатель data у объектов t1 и t2 указывает на одну и туже ячейку памяти, при завершении у одного объекта уже адрес у указателя удален, а у второго объекта указатель продолжает указывать на ту ячейку памяти, но адрес уже удален и повторное удаление невозможно, так как ячейка памяти уже очищена. В таких случаях используется глубокое копирование.
#include <iostream>
#include <cstring>
class Text {
private:
char * data;
int length;
public:
//Конструктор по умолчанию
Text(const char * str) {
length = strlen(str);
data = new char[length + 1];
strcpy(data, str);
std::cout << "Default constructor, data: " << &data << std::endl;
}
//Конструктор копирвания
Text(const Text & other) {
length = other.length;
data = new char[length + 1];
std::strcpy(data, other.data
);
std::cout << "Copy constructor, data: "<< &data << std::endl;
}
// Оператор=
Text & operator=(const Text & other) {
if (this == &other) return *this;
delete[] data; // сначала очищаем указатель
length = other.length;
data = new char[length + 1]; //добавляем новый адрес
std::strcpy(data, other.data
);
std::cout << "Operator=, this data: "<< &data << std::endl;
std::cout << "Operator=, other data: "<< &other.data << std::endl;
return *this;
}
~Text() {
delete[] data;
}
};
int main() {
Text t1("Hello");
std::cout << "============" << std::endl;
Text t2 = t1;
std::cout << "============" << std::endl;
Text t3("Hi");
std::cout << "============" << std::endl;
t3 = t1;
return 0;
}
Консоль:
Default constructor, data: 0xffffd4a86330
============
Copy constructor, data: 0xffffd4a86320
============
Default constructor, data: 0xffffd4a86310
============
Operator=, this data: 0xffffd4a86310
Operator=, other data: 0xffffd4a86330
Здесь добавился конструктор копирования (в отличие от конструктора по умолчанию можем заметить что принимает тип Text, а не const char *), перегруженный оператор присваивания (как раз-таки, когда у нас при двух созданных объектах один приравнивается к другому).
Специальные функции-члены, генерируемые компилятором
1. Автоматически создаются, если не определены:
2. Конструктор по умолчанию
3. Конструктор копирования
4. Операция присваивания
5. Деструктор
6. Операция взятия адреса
Конструктор копирования по умолчанию — почленное копирование (поверхностное).
Операция присваивания по умолчанию — почленное присваивание.