Особенности наследования классов в C++

Информация - Компьютеры, программирование

Другие материалы по предмету Компьютеры, программирование

? в порядке их наследования;

(2) конструкторы невиртуальных базовых классов в порядке их наследования;

(3) конструкторы всех компонентных классов.

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

 

#include Base1 {() { cout<< "Создание Base1"<<endl; }

};Base2 {() { cout<< "Создание Base2"<<endl; }

};Base3 {() { cout<< "Создание Base3"<<endl; }

};Base4 {() { cout<< "Создание Base4"<<endl; }

};Derived: private Base1, private Base2,private Base3 {anObject;() {}

};main() {anObject;

}

На выходе этой программы будет следующее:

Создание Base1

Создание Base2

Создание Base3

Создание Base4

Добавление в конструктор для Derived конкретных вызовов с другим порядком и повторение программы не изменит сообщения после выполнения этой программы.

Derived: private Base1, private Base2, private Base3 {anObject;(): anObject(), Base3(), Base2(), Base1() {}

};

 

Изменение порядка наследования классов изменит последова- тельность появления сообщений на выходе программы. Объявив Derived следующим образом:

Derived: private Base3, private Base2, private Base1 {anObject;(): anObject(), Base1(), Base2(), Base3() {}

};

 

можно получить на выходе программы:

Создание Base3

Создание Base2

Создание Base1

Создание Base4

Последовательность вызова деструкторов будет обратной относительно последовательности вызова конструкторов.

Необходимость использования в программе множественного наследования чаще всего говорит о плохой структуризации классов. Это могло возникнуть в том случае, если программист уделил недостаточно времени для предварителього анализа предметной области программы на самом первом этапе ее составления. Структурный подход к объектно-ориентированному проектированию предполагает более четкую организацию классов.

 

3. Адреса базовых классов

 

Большое число проблем при проектировании объектно-ориентированных программ возникает в связи со способом размещения производных классов в памяти. Одна из ключевых причин того, что указатель подкласса может передаваться как указатель суперкласса, состоит в том, что когда-то суперкласс впервые появляется в подклассе. Рассмотрим простой пример наследования в классах.

Base {a;b;f1();Derived: public Base {c;

} object;

 

Рассматривая размещение объекта производного класса Derived в памяти, например, с помощью команды отладчика Inspect, можно получить несколько упрощенную графическую диаграмму, которая приведена на рис. 1.

 

Base** Base

Derived

 

 

Рис. 1. Схема размещения в памяти простого производного класса

 

Если Derived* передан в функцию, ожидающую получения Base*, никаких проблем не возникает. Класс Derived совпадает с классом Base во всем, что касается его части, перекрывающейся с Base. То же самое касается и вызова функции object.f1(). Передаваемый указатель this имеет одно и то же значение безотносительно к типу. Однако в отношении класса Derived с множественным наследованием сказанное ранее будет несправедливо.

Рассмотрим пример.

программный наследование класс

struct Base1 {a; float b;f1();

};Base2 {c;d;f2();

};Derived: public Base1, public Base2 {e;

} object;

 

Примерная схема памяти для этого случая показана на рис. 2.

Класс Base2 более не находится в начале класса Derived. Если попробовать передать Derived* функции, ожидающей поступления Base1*, то проблем не возникнет. Вместе с тем, при вызове функции, ожидающей поступления Base2*, полученный ей адрес окажется неправильным.

Чтобы исправить этот адрес, необходимо прибавить к адресу Derived::object смещение Base2 в Derived, таким образом, чтобы результат указывал на ту часть, которая относится к Base2. Такая же коррекция должна выполняться для каждого случая приведения типа указателей из Derived* в Base2*, включая и скрытый указатель this, передаваемый компонентным функциям Base2.

 

Base1**

Base 1

 

* Derived

 

Base 2

Рис. 2. Схема размещения в памяти класса с множественным наследованием

object;.f2() // Перед передачей в f2() адрес объекта object

// должен быть соответственно скорректирован

По тем же причинам C++ также должен выполнить коррекцию и при обратном приведении типа из Base2* в Derived*.

 

 

4. Виртуальное наследование

 

В следующем примере класс Derived наследует свойства двух копий класса Base: одной через класс FirstBase, а второй - через SecondBase:

Base {object;

};FirstBase: public Base {a;

};SecondBase: public Base {b;Derived: public FirstBase, public SecondBase {dObject;

};

 

Примерная схема памяти для объекта класса Derived показана на рис. 3.

 

 

Base

FirstBase

 

 

 

Base Derived

SecondBase

Рис. 3. Схема размещения в памяти множественного производного класса

 

Чтобы позволить наследование в таких случаях одной и той же копии Base, в C++ необходимо включить в команду наследования ключевое слово virtual. В этом случае, программу можно переписать следующим образом:

Base {object;

};FirstBase: virtual public Base {a;

};SecondBase: virtual public Base {b;Derived: virtual public FirstBase,public SecondBase {dObject;

};

Такая модификация программы изменит схему размещения объекта класса Derived, как показано на рис. 4.

 

 

int object Base

 

int a FirstBase

 

float b SecondBase Derived

long

dObject

Рис. 4. Схема размещения в памяти виртуального множественного производного класса

 

Теперь существует всего одна копия класса Base.

 

 

Заключение

 

Наследование в C++ позволяет вам строить /порождать) новый класс из существующего класса. Строя таким способом один класс из другого, вы уменьшаете объем программирования, что, в свою очередь, экономит ваше время. C++ позволяет вам порождать класс из дву?/p>