Программирование >>  Программирование на языке c++ 

1 ... 153 154 155 [ 156 ] 157 158 159


теле этой функции. Любой автоматический объект конструируется тогда, когда встречается его объявление, и разрушается, когда блок, в котором он описан, прекращает существовать. После завершения функция f прекращает существовать. В результате вызывается деструктор объекта о.

Таким образом, выполняются следующие действия:

♦ описание X о; задает конструирование нового объекта о. Конструктор объекта о вьщеляет (динамически) память с помощью оператора new;

♦ вызывается функция f;

♦ значение объекта о копируется из функции main в стек функции f;

♦ копия объекта о содержит указатель на ту же динамическую память (указатели на динамическую память в объекте-оригинале и в объекте-копии имеют одни и те же значения);

♦ функция f завершается;

♦ вызывается деструктор для копии объекта о, который разрушает динамически выделенную память;

♦ теперь указатель в оригинальном объекте адресует несуществующую (удаленную) память.

Если объявить функцию f в виде f(X&), то ошибка будет устранена. При необходимости можно оставить и предьщу-щее объявление. В этом случае надо устранить ошибку в самом классе X. Когда объект о копируется из функции main в функцию f, то вызывается соответствующий конструктор для копирования. Поскольку в нашем классе такого конструктора нет, то вызывается конструктор, заданный по умолчанию. Этот конструктор строит точную копию всех данных объекта о, что, в конечном счете, и приводит к ошибке. Если в классе X задать явно конструктор для копирования, например:

X::X(const Х& о)

{ i = newint[2]; i[0] = o.i[0]; i[1] = o.i[1]; }

TO ошибка будет устранена. Таким образом, если в конструкторе некоторого класса X осуществляется динамическое выделение памяти, такой класс должен иметь соответствующий конструктор для копирования, а также деструктор (для осво-



бождения памяти). Аналогичное правило распространяется и на оператор присваивания (=), поскольку оператор присваивания, заданный по умолчанию, порождает те же проблемы.

Дополнительную информацию по рассмотренным вопросам можно найти в работе [16].

2.2. Виртуальные функции. Если в некотором классе задана хотя бы одна виртуальная функция, то все объекты этого класса содержат указатель на связанную с их классом виртуальную таблицу. Эта виртуальная таблица содержит адреса (указатели на первые инструкции) действительных функций, которые будут вызваны. Пусть заданы базовый В и производный D классы (B<-D). В базовом классе В задана виртуальная функция yi. В производном классе D содержится улучшенная версия этой функции vf. Рассмотрим некоторую глобальную функцию f(B *pb) и объявления: В Ь; D d;. Предположим, что выполняются следующие действия: ♦ в функцию f передается указатель на объект. Для вызова f(&b) это будет указатель на объект базового класса, а для вызова f(&d) - указатель на объект производного класса;

♦ в теле функции f вызывается виртуальная функция vf (pb->vf(...);). В результате выполняются следующие дей-, ствия. В объекте с заданным адресом находится адрес виртуальной таблицы. Если pb - адрес базового объекта, то находится адрес виртульной таблицы базового объекта. Если pb - адрес производного объекта, то находится адрес виртуальной таблицы производного объекта. Имя функции vf задает смещение в соответствующей виртуальной таблице для нахождения указателя на функцию vf (адреса первой инструкции в теле функции vf). После выбора первой инструкции функции vf она выполняется как обычно. Более детальное пояснение описанных действий с рисунками и программами на языке ассемблера дано в [16].

Если класс имеет хотя бы одну виртуальную функцию, то он должен иметь виртуальный деструктор [1]. Это позволяет устранить проблемы, связанные с разрушением объектов производного класса. Рассмотрим пример: void main(void)

{ В *pb = new D; delete pb; }



Здесь pb - указатель на объект базового класса В, но в действительности рЪ указывает на объект производного класса D. Если деструктор в классе В не виртуальный, то мы будем иметь такую последовательность вызовов конструкторов и деструкторов: 1) конструктор В; 2) конструктор D; 3) деструктор В. В результате один деструктор (для объекта D) не вызван. Объявление деструктора виртульным в классе В устраняет эту проблему.

2.3. Виртуальные базовые классы. Рассмотрим пример:

void main(void)

{ В b; D1 d1; B<-D1

D2 d2; B<-D2

D1D2 d1d2; } (D1,D2)<-D1D2

Для невиртульного базового класса В каждый объект класса D1D2 имеет два подобъекта класса В, т.е. dld2 имеет такую структуру в памяти компьютера, что в ней область данных объекта b встречается два раза. Если это порождает проблемы, то класс В может быть объявлен виртуальным. В этом случае каждый объект класса D1D2 имеет только один подобъект класса В, т.е. dld2 имеет такую структуру в памяти компьютера, что в ней область данных объекта b встречается один раз, и доступ к этой области из объекта dld2, а также из подобъектов dl, d2 объекта dld2 осуществляется через ее адрес (через адрес подобъекта b в структуре dld2). Более детальные пояснения по этому вопросу с графическими иллюстрациями даны в работе [16].

2.4. Объекты, действующие как указатели (smart pointers). Для того чтобы создать такие объекты, необходимо переопределить оператор ->. Рассмотрим пример:

template <class Т> class Р { Т *р;

public: Р(Т *тр = NULL) : p(mp) { }

~Р(){ delete р; } Т* operator->() const { return р; } };

Теперь, чтобы получить доступ к элементам класса Т, объекты класса Р можно использовать так же, как указатели. Рассмотрим следующую функцию:

void F(int I)

{ Р<Х> px = new X(l); px->fX(); }



1 ... 153 154 155 [ 156 ] 157 158 159

© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки.
Яндекс.Метрика