Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Лекции по Технологии разработки ПО 2005.doc
Скачиваний:
0
Добавлен:
01.05.2025
Размер:
833.54 Кб
Скачать

Абстрактные классы и чистые виртуальные функции

Иногда, разрабатывая иерархию классов, можно получить базовый класс, для которого создание объекта как бы теряет смысл. В предыдущей программе это хорошо прослеживается. Классы, для которых нет смысла создавать объекты, объявляют как абстрактные. Базовый класс, объекты которого никогда не будут реализованы, называется абстрактным классом. Такой класс может существовать с единственной целью – быть родительским по отношению к производным классам, объекты которых будут реализованы.

Абстрактный класс содержит хотя бы один чистый виртуальный метод, т.е. метод, объявленный в классе, но не имеющий конкретной реализации. Синтаксис объявления чистой виртуальной функции дополняется конструкцией =0.

class Base { //абстрактный Базовый класс

public:

virtual void show()=0; //чистая Виртуальная функция

//{ cout << "Base\n"; } уже не надо!!!

};

… тоже самое

int main() {

// Base b; //невозможно создание объекта абстрактного класса

… тоже самое

}

В результате, программа выведет:

Derv1

Derv2

Здесь, конструкция =0 – это просто способ сообщить компилятору, что функция будет чистой виртуальной. Если в классе используется чистая виртуальная функция, то он становится абстрактным и никакие объекты из него реализовать не удастся. Компилятор не допустит создания объекта для абстрактного класса.

Виртуальные деструкторы

Допустим, чтобы удалить объект производного класса, вы выполнили delete над указателем базового класса, указывающим на порождённый класс. Если деструктор базового класса не является виртуальным, тогда delete, будучи обычным методом, вызовет деструктор для базового класса вместо того, чтобы запустить деструктор для производного класса.

class Base {

public:

~Base() //невиртуальный деструктор

// virtual ~Base() //виртуальный деструктор

{ cout << "Base udalen\n"; }

};

//---------------------------------------------------------------------------

class Derv : public Base {

public:

~Derv()

{ cout << "Derv udalen\n"; }

};

//---------------------------------------------------------------------------

int main() {

Base* pBase = new Derv;

delete pBase;

getch(); return 0;

}

Программа выдаёт такой результат: Base udalen . Это говорит о том, что деструктор производного класса не вызывается вообще! Если написать virtual ~Base(), то обе части объекта порождённого класса будут удалены корректно и программа выведет: Derv udalen; Base udalen.

Виртуальные базовые классы или устранение неоднозначности при множественном наследовании

Рассмотрим следующую ситуацию.

Parent

Child1

Child2

Vnuk

Имеется базовый класс Parent, есть два порождённых класса Child1, Child2 и есть класс Vnuk, порождённый одновременно классами Child1 и Child2 (см. рис.). В такой ситуации проблемы могут возникнуть, если метод класса Vnuk захочет получить доступ к данным или методам класса Parent.

class Parent {

protected:

int basedata;

};

class Child1 : public Parent

{ };

class Child2 : public Parent

{ };

class Vnuk : public Child1, public Child2 {

public:

int getdata()

{ return basedata; } // ОШИБКА: неоднозначность

};

Здесь, при попытке метода int getdata() получить доступ к переменной базового класса basedata возникнет ошибка. Дело в том, что каждый из порождённых классов (Child1 и Child2) наследует свою копию базового класса Parent. Это копия называется подобъектом. Каждый из двух подобъектов содержит собственную копию данных базового класса, включая basedata. Здесь и возникает неоднозначная ситуация. Для устранения того рода неоднозначности классы Child1 и Child2 необходимо сделать наследниками виртуального базового класса, что приведет к созданию только одной общей копии базового класса Parent.

… тоже самое

class Child1 : virtual public Parent

{ };

class Child2 : virtual public Parent

… тоже самое

Приведём более конкретный пример.

class A {

protected:

int x;

public:

A() { x=0; }

A(int xx) { x=xx; }

};

//---------------------------------------------------------------------------

class B : virtual public A {

public:

void addB(int y) { x=x+y; }

};

//---------------------------------------------------------------------------

class C : virtual public A {

public:

void addC(int y) { x=x*y; }

};

//---------------------------------------------------------------------------

class Vnuk : public B, public C {

public:

void show() { cout << "X= " << x << endl; } //если-бы не virtual, то здесь возникла бы неоднозначность

};

//---------------------------------------------------------------------------

int main() {

Vnuk v1;

v1.show(); // 0

v1.addB(10); v1.show(); // 10

v1.addC(5); v1.show(); // 50

getch(); return 0;

}

Добавив спецификатор virtual перед модификаторами доступа к базовому классу А в объявлениях классов В и С, мы включили в класс Vnuk только одну копию класса А, тем самым устранили проблему неоднозначности.