- •Лабораторная работа №5
- •Теоретические сведения
- •Определение класса
- •Управление доступом
- •Элементы класса
- •Данные-элементы
- •Элементы-функции
- •Доступ к данным-элементам
- •Вызов функций-элементов
- •Указатель this
- •Конструктор
- •Конструктор копирования
- •Операции
- •Явное присваивание в выражении
- •Инициализация
- •Примеры
- •Неявный конструктор копирования
- •Явный конструктор копирования
- •Деструктор
- •Задание 1
- •Задание 2
Операции
Объекту может быть присвоено значение при помощи одного из двух способов:
Явное присваивание в выражении
Инициализация
Явное присваивание в выражении
Object A;
Object B;
A = B; // транслируется как Object::operator=(const Object&), таким образом вызывается A.operator=(B)
Инициализация
Объект может быть инициализирован любым из следующих способов.
a. При помощи объявления
Object B = A; // транслируется как Object::Object(const Object&)
b. При помощи аргументов функции
type function (Object a);
c. При помощи возвращаемого значения функции
Object a = function();
Конструктор копирования используется только в последнем случае (инициализации) и не используется вместо присваивания (т.е. там, где используется оператор присваивания).
Неявный конструктор копирования класса вызывает базовые конструкторы копирования и копии их членов, соответствующие их типу. Если это тип класса, то вызывается конструктор копирования. Если это скалярный тип, то используется встроенный оператор присваивания. И наконец, если это массив, то каждый элемент копируется соответствующим их типу образом.[2]
Применением явного конструктора копирования программист может определить дальнейшие действия после копирования объекта.
Примеры
Следующие примеры иллюстрируют как работают конструкторы копирования и почему они иногда требуются.
Неявный конструктор копирования
Рассмотрим следующий пример.
#include <iostream>
class Person
{
public:
int age;
Person(int age) : age(age) {}
};
int main()
{ Person timmy(10);
Person sally(15);
Person timmy_clone = timmy;
std::cout << timmy.age << " " << sally.age << " " << timmy_clone.age << std::endl;
timmy.age = 23;
std::cout << timmy.age << " " << sally.age << " " << timmy_clone.age << std::endl;}
Результат
10 15 10
23 15 10
Как и ожидалось, timmy скопировался в новый объект timmy_clone. При изменении возраста (age) timmy, у timmy_clone возраст не менялся. Это потому, что они являются полностью независимыми объектами.
Компилятор сгенерировал для нас конструктор копирования, который может быть записан примерно так:
Person(Person const& copy)
: age(copy.age) {}
Так когда же нам реально требуется явный конструктор копирования? В следующем подразделе рассмотрим этот вопрос.
Явный конструктор копирования
Теперь рассмотрим очень простой класс динамических массивов, как например, нижеследующий:
#include <iostream>
class Array
{ public:
int size;
int* data;
Array(int size)
: size(size), data(new int[size]) {}
~Array()
{ delete[] data;
}
};
int main()
{ Array first(20);
first.data[0] = 25;
{
Array copy = first;
std::cout << first.data[0] << " " << copy.data[0] << std::endl;
} // (1)
first.data[0] = 10; // (2)}
Результат
25 25
Segmentation fault
Хотя мы не указывали конструктор копирования, компилятор сгенерировал его для нас. Генерируемый конструктор выглядит примерно так:
Array(Array const& copy)
: size(copy.size), data(copy.data) {}
Проблема, связанная с этим конструктором, заключается в том, что он выполняет простое копирование указателя data. Он только копирует адрес, а не сами данные. И когда программа доходит до строчки (1), вызывается деструктор copy (объекты в стеке уничтожаются автоматически при достижении их границ). Как видно, деструктор Array удаляет массив data, поэтому когда он удаляет данные copy, он также удаляет данные first. Строка (2)теперь получает неправильные данные и записывает их! Это и приводит к знаменитой ошибке сегментации (segmentation fault).
Если напишем наш собственный конструктор копирования, выполняющий глубокое копирование, то этой проблемы не возникнет.
Array(Array const& copy)
: size(copy.size), data(new int[copy.size])
{ std::copy(copy.data, copy.data + copy.size, data); // #include <algorithm> для std::copy
}
Здесь мы создаем новый массив int и копируем содержимое в него. Теперь, деструктор copy только удалит его данные и не тронет данные first. Строка (2) больше не вызывает ошибку сегментации.
Вместо выполнения глубокого копирования можно использовать несколько оптимизирующих стратегий. Это позволит вам безопасным способом разрешить доступ к данным для нескольких объектов, тем самым экономя память. Стратегия копирование при записи создает копию данных только когда их записывает. Счетчик ссылок содержит счетчик количества объектов ссылающихся на данные и удаляет его только тогда, когда счетчик доходит до нуля (например, boost::shared_ptr).