Derived classes
Любой классовый тип (независимо от объявленного с помощью class-key class или struct ) может быть объявлен как производный от одного или нескольких базовых классов , которые, в свою очередь, могут быть производными от своих собственных базовых классов, формируя иерархию наследования.
Содержание |
Синтаксис
Список базовых классов предоставляется в
base-clause
синтаксиса
объявления класса
.
base-clause
состоит из символа
:
, за которым следует разделённый запятыми список одного или нескольких
base-specifier
ов.
| attr (необязательно) class-or-computed | (1) | ||||||||
attr
(необязательно)
virtual
class-or-computed
|
(2) | ||||||||
| attr (необязательно) access-specifier class-or-computed | (3) | ||||||||
attr
(необязательно)
virtual
access-specifier
class-or-computed
|
(4) | ||||||||
attr
(необязательно)
access-specifier
virtual
class-or-computed
|
(5) | ||||||||
virtual
и
access-specifier
могут появляться в любом порядке.
| attr | - | (since C++11) последовательность любого количества атрибутов | ||||
| access-specifier | - | один из private , public , или protected | ||||
| class-or-computed | - |
один из
|
Уточняющий спецификатор типа elaborated type specifier не может напрямую использоваться в качестве class-or-computed из-за синтаксических ограничений.
|
base-specifier s in a base-clause may be pack expansions .
A class or struct declared
|
(since C++11) |
|
base-specifier в base-clause могут быть pack expansions .
Класс или структура, объявленные
|
(since C++11) |
Если access-specifier опущен, по умолчанию он устанавливается в public для производных классов, объявленных с помощью class-key struct , и в private для производных классов, объявленных с помощью class-key class .
struct Base { int a, b, c; }; // каждый объект типа Derived включает Base как подобъект struct Derived : Base { int b; }; // каждый объект типа Derived2 включает Derived и Base как подобъекты struct Derived2 : Derived { int c; };
Классы, обозначенные class-or-computed , перечисленные в base-clause , являются прямыми базовыми классами. Их базовые классы являются косвенными базовыми классами. Один и тот же класс не может быть указан в качестве прямого базового класса более одного раза, но один и тот же класс может быть одновременно прямым и косвенным базовым классом.
Каждый прямой и косвенный базовый класс присутствует, как base class subobject , в представлении объекта производного класса по ABI-зависимому смещению. Пустые базовые классы обычно не увеличивают размер производного объекта благодаря empty base optimization . Конструкторы подобъектов базовых классов вызываются конструктором производного класса: аргументы могут быть переданы этим конструкторам в member initializer list .
Виртуальные базовые классы
Для каждого отдельного базового класса, который указан как virtual , наиболее производный объект содержит только один подобъект базового класса этого типа, даже если класс появляется много раз в иерархии наследования (при условии, что он наследуется virtual каждый раз).
struct B { int n; }; class X : public virtual B {}; class Y : virtual public B {}; class Z : public B {}; // каждый объект типа AA содержит один X, один Y, один Z и два B: // один является базовым для Z и один общий для X и Y struct AA : X, Y, Z { AA() { X::n = 1; // изменяет член виртуального подобъекта B Y::n = 2; // изменяет член того же виртуального подобъекта B Z::n = 3; // изменяет член невиртуального подобъекта B std::cout << X::n << Y::n << Z::n << '\n'; // выводит 223 } };
Примером иерархии наследования с виртуальными базовыми классами является иерархия потоков ввода-вывода стандартной библиотеки: std::istream и std::ostream наследуются от std::ios с использованием виртуального наследования. std::iostream наследуется и от std::istream , и от std::ostream , поэтому каждый экземпляр std::iostream содержит подобъект std::ostream , подобъект std::istream и только один подобъект std::ios (и, следовательно, один std::ios_base ).
Все виртуальные базовые подобъекты инициализируются до любых невиртуальных базовых подобъектов, поэтому только наиболее производный класс вызывает конструкторы виртуальных баз в своем списке инициализации членов :
struct B { int n; B(int x) : n(x) {} }; struct X : virtual B { X() : B(1) {} }; struct Y : virtual B { Y() : B(2) {} }; struct AA : X, Y { AA() : B(3), X(), Y() {} }; // конструктор по умолчанию AA вызывает конструкторы по умолчанию X и Y // но эти конструкторы не вызывают конструктор B, потому что B является виртуальным базовым классом AA a; // a.n == 3 // конструктор по умолчанию X вызывает конструктор B X x; // x.n == 1
Для неквалифицированного поиска имён членов класса при наличии виртуального наследования существуют специальные правила (иногда называемые правилами доминирования).
Публичное наследование
Когда класс использует public спецификатор доступа члена для наследования от базового класса, все публичные члены базового класса доступны как публичные члены производного класса, а все защищённые члены базового класса доступны как защищённые члены производного класса (приватные члены базового класса никогда не доступны, если не объявлены дружественными).
Публичное наследование моделирует отношение подтипов в объектно-ориентированном программировании: объект производного класса ЯВЛЯЕТСЯ объектом базового класса. Ожидается, что ссылки и указатели на производный объект могут использоваться любым кодом, который ожидает ссылки или указатели на любой из его публичных базовых классов (см. LSP ) или, в терминах DbC , производный класс должен сохранять инварианты класса своих публичных базовых классов, не должен усиливать никакие предусловия или ослаблять никакие постусловия функций-членов, которые он переопределяет .
#include <iostream> #include <string> #include <vector> struct MenuOption { std::string title; }; // Menu - это вектор MenuOption: опции могут быть вставлены, удалены, переупорядочены... // и имеет заголовок. class Menu : public std::vector<MenuOption> { public: std::string title; void print() const { std::cout << title << ":\n"; for (std::size_t i = 0, s = size(); i < s; ++i) std::cout << " " << (i + 1) << ". " << at(i).title << '\n'; } }; // Примечание: Menu::title не проблематичен, так как его роль независима от базового класса. enum class Color { WHITE, RED, BLUE, GREEN }; void apply_terminal_color(Color) { /* OS-specific */ } // ЭТО ПЛОХО! // ColorMenu - это Menu, где каждая опция имеет пользовательский цвет. class ColorMenu : public Menu { public: std::vector<Color> colors; void print() const { std::cout << title << ":\n"; for (std::size_t i = 0, s = size(); i < s; ++i) { std::cout << " " << (i + 1) << ". "; apply_terminal_color(colors[i]); std::cout << at(i).title << '\n'; apply_terminal_color(Color::WHITE); } } }; // ColorMenu требует следующих инвариантов, которые не могут быть удовлетворены // при публичном наследовании от Menu, например: // - ColorMenu::colors и Menu должны иметь одинаковое количество элементов // - Чтобы иметь смысл, вызов erase() должен также удалять элементы из colors, // чтобы позволить опциям сохранять свои цвета // В основном каждый неконстантный вызов метода std::vector нарушит инвариант // ColorMenu и потребует исправления от пользователя путем корректного управления цветами. int main() { ColorMenu color_menu; // Основная проблема этого класса в том, что мы должны поддерживать синхронизацию // ColorMenu::colors с Menu. color_menu.push_back(MenuOption{"Some choice"}); // color_menu.print(); // ОШИБКА! colors[i] в print() выходит за пределы диапазона color_menu.colors.push_back(Color::RED); color_menu.print(); // OK: colors и Menu имеют одинаковое количество элементов }
Защищённое наследование
Когда класс использует protected спецификатор доступа члена для наследования от базового класса, все публичные и защищенные члены базового класса доступны как защищенные члены производного класса (приватные члены базового класса никогда не доступны, если не объявлены дружественными).
Защищённое наследование может использоваться для «контролируемого полиморфизма»: внутри членов Derived, а также внутри членов всех последующих производных классов, производный класс ЯВЛЯЕТСЯ базовым: ссылки и указатели на Derived могут использоваться там, где ожидаются ссылки и указатели на Base.
Приватное наследование
Когда класс использует private спецификатор доступа члена для наследования от базового класса, все публичные и защищённые члены базового класса доступны как приватные члены производного класса (приватные члены базового класса никогда не доступны, кроме случаев объявления дружественности).
Приватное наследование обычно используется в политико-ориентированном проектировании, поскольку политики обычно являются пустыми классами, и их использование в качестве базовых классов обеспечивает как статический полиморфизм, так и преимущества оптимизации пустого базового класса .
Приватное наследование также может использоваться для реализации отношения композиции (подобъект базового класса является деталью реализации объекта производного класса). Использование члена класса обеспечивает лучшую инкапсуляцию и обычно предпочтительнее, если только производный класс не требует доступа к защищённым членам (включая конструкторы) базы, не нуждается в переопределении виртуального члена базы, не требует чтобы база конструировалась до и разрушалась после некоторого другого базового подобъекта, не нуждается в разделении виртуальной базы или не требует контроля над конструированием виртуальной базы. Использование членов для реализации композиции также неприменимо в случае множественного наследования от parameter pack или когда идентичности базовых классов определяются во время компиляции через метапрограммирование шаблонов.
Подобно защищенному наследованию, приватное наследование также может использоваться для контролируемого полиморфизма: внутри членов производного класса (но не внутри дальнейших производных классов), производный класс ЯВЛЯЕТСЯ базовым.
template<typename Transport> class service : private Transport // приватное наследование от политики Transport { public: void transmit() { this->send(...); // отправка с использованием предоставленного транспорта } }; // Политика TCP транспорта class tcp { public: void send(...); }; // Политика UDP транспорта class udp { public: void send(...); }; service<tcp> service(host, port); service.transmit(...); // отправка через TCP
Поиск имени члена
Правила поиска неквалифицированных и квалифицированных имён для членов класса подробно описаны в разделе name lookup .
Ключевые слова
Отчеты о дефектах
Следующие отчеты об изменениях поведения, влияющие на дефекты, были применены задним числом к ранее опубликованным стандартам C++.
| DR | Applied to | Behavior as published | Correct behavior |
|---|---|---|---|
| CWG 1710 | C++98 |
синтаксис
class-or-decltype
делал невозможным наследование от
зависимого класса, где требуется template дезинтегратор |
разрешено template |