Особенности наследования классов в 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>