Освой самостоятельно С++ за 21 день.
Шрифт:
Рис. 10.2 Возникновение ошибочного указателя
Чтобы предупредить возникновение подобных проблем, нужно вместо копировщика по умолчанию создать и использовать собственный копировщик, который будет осуществлять глубинное копирование с перемещением значений переменных-членов в новые адреса памяти. Этот процесс показан
Листинг 10.5. Конструктор-копировщик
1: // Листинг 10.5.
2: // Конструктор-копировщик
3:
4: #include <iostream.h>
5:
6: class CAT
7: {
8: public:
9: CAT; // конструктор по умолчанию
10: CAT (const CAT &); // конструктор-копировщик
11: ~CAT; // деструктор
12: int GetAge const { return *itsAge; }
13: int GetWeight const { return *itsWeight; }
14: void SetAge(int age) { *itsAge = age; }
15:
16: private:
17: int *itsAge;
18: int *itsWeight;
19: };
20:
21: CAT::CAT
22: {
23: itsAge = new int;
24: itsWeight = new int;
25: *itsAge = 5;
26: *itsWeight = 9;
27: }
28:
29: CAT::CAT(const CAT & rhs)
30: {
31: itsAge = new int;
32: itsWeight = new int;
33: *itsAge = rhs.GetAge; // открытый метод доступа
34: *itsWeight = *(rhs.itsWeight); // закрытый метод доступа
35: }
36:
37: CAT::~CAT
38: {
39: delete itsAge;
40: itsAge = 0;
41: delete itsWeight;
42: itsWeight = 0;
43: }
44:
45: int main
46: {
47: CAT frisky;
48: cout << "frisky's age: " << frisky.GetAge << endl;
49: cout << "Setting frisky to 6...\n";
50: frisky.SetAge(6);
51: cout << "Creating boots from frisky\n";
52: CAT boots(frisky);
53: cout << "frisky's age: " << frisky.GetAge << endl;
54: cout << "boots' age; " << boots.GetAge << endl;
55: cout << "setting frisky to 7...\n";
56: frisky.SetAge(7);
57: cout << "frisky's age: " << frisky.GetAge << endl;
58: cout << "boot's age: " << boots.GetAge << endl;
59: return 0;
60: }
Результат:
frisky's age: 5
Setting frisky to 6...
Creating boots from frisky
frisky's age: 6
boots' age: 6
setting frisky to 7...
frisky's age: 7
boots' age: 6
Анализ: В строках программы с 6 по 19 объявляется класс CAT. Обратите внимание, что в строке 9 объявляется конструктор по умолчанию, а в строке 10 — конструктор-копировщик.
В строках 17 и 18 объявляется две переменные-члены, представляющие собой указатели на целочисленные значения. В реальной жизни трудно вообразить, для чего может понадобиться создание переменных-членов как указателей на целочисленные значения. Но в данном случае такие переменные являются отличными объектами для демонстрации методов управления переменными-членами, сохраненными в динамической области памяти.
Конструктор по умолчанию в строках с 21 по 27 выделяет для переменных области динамической памяти и инициализирует эти переменные.
Работа копировщика начинается со строки 29. Обратите внимание, что в копировщике задан параметр rhs. Использовать в параметрах копировщиков символику rhs, что означает right-hand side (стоящий справа), — общепринятая практика. Если вы посмотрите на строки 33 и 34, то увидите, что в выражениях присваивания имена параметров копировщика располагаются справа от оператора присваивания (знака равенства).
Вот как работает копировщик. Строки 31 и 32 выделяют свободные ячейки в области динамической памяти. Затем, в строках 33 и 34 в новые ячейки переписываются значения из существующего класса CAT.
Параметр rhs соответствует объекту классу CAT, который передается в копировщик в виде константной ссылки. Как объект класса CAT, rhs содержит все переменные- члены любого другого класса CAT.
Любой объект класса CAT может открыть доступ к закрытым переменным-членам для любого другого объекта класса CAT. В то же время для внешних обращений всегда лучше создавать открытые члены, где это только возможно. Функция-член rhs.GetAge возвращает значение, сохраненное в переменной-члене itsAge, адрес которой представлен в rhs.
Процедуры, осуществляемые программой, продемонстрированы на рис. 10.3. Значение, на которое ссылалась переменная-член исходного класса CAT, копируется в новую ячейку памяти, адрес которой представлен в такой же переменной-члене нового класса CAT.
В строке 47 вызывается объект frisky из класса CAT. Значение возраста, заданное в frisky, выводится на экран, после чего в строке 50 переменной возраста присваивается новое значение — 6. В строке 52 методом копирования объекта frisky создается новый объект boots класса CAT. Если бы в качестве параметра передавался объект frisky, то вызов копировщика осуществлялся бы компилятором.
В строках 53 и 54 выводится возраст обеих кошек. Обратите внимание, что в обоих случаях в объектах frisky и boots записан возраст 6, тогда как если бы объект boots создавался не методом копирования, то по умолчанию было бы присвоено значение 5. В строке 56 значение возраста в объекте было изменено на 7 и вновь выведены на экран значения обоих объектов. Значение объекта frisky действительно изменилось на 7, тогда как в boots сохранилось прежнее значение возраста 6. Это доказывает, что переменная объекта frisky была скопирована в объект boots по новому адресу.