Производные классы
Любой тип класса (объявленный с ключевым словом class-key class или struct) может быть объявлен производным от одного или нескольких базовых классов, которые, в свою очередь, могут быть производными от собственных базовых классов, образуя иерархию наследования.
Синтаксис
Список базовых классов указывается в base-clause синтаксиса объявления класса. base-clause состоит из символа :, за которым следует список из одного или нескольких base-specifier, разделённых запятыми.
| attr (optional) class-or-computed | (1) | |
attr (optional) virtual class-or-computed | (2) | |
| attr (optional) access-specifier class-or-computed | (3) | |
attr (optional) virtual access-specifier class-or-computed | (4) | |
attr (optional) access-specifier virtual class-or-computed | (5) |
virtual и access-specifier могут располагаться в любом порядке.| attr | - | (начиная с C++11) последовательность из любого количества атрибутов | ||||
| access-specifier | - | одно из значений private, public или protected | ||||
| class-or-computed | - | одно из следующих значений:
|
Из-за ограничений синтаксиса уточнённый спецификатор типа уточнённый спецификатор типа не может непосредственно выступать в роли class-or-computed.
| base-specifier в base-clause могут быть раскрытиями пакета. Класс или структура, объявленные | (начиная с C++11) |
Если access-specifier опущен, для производных классов, объявленных с ключевым словом class-key struct, по умолчанию используется public, а для производных классов, объявленных с ключевым словом class-key class, — private.
struct Base
{
int a, b, c;
};
// every object of type Derived includes Base as a subobject
struct Derived : Base
{
int b;
};
// every object of type Derived2 includes Derived and Base as subobjects
struct Derived2 : Derived
{
int c;
};
Классы, обозначенные элементами class-or-computed в base-clause, являются прямыми базовыми классами. Их базовые классы являются косвенными базовыми классами. Один и тот же класс нельзя указать более одного раза в качестве прямого базового класса, однако один и тот же класс может быть одновременно прямым и косвенным базовым классом.
Каждый прямой и косвенный базовый класс присутствует в представлении объекта производного класса как подобъект базового класса со смещением, зависящим от ABI. Пустые базовые классы обычно не увеличивают размер производного объекта благодаря оптимизации пустого базового класса. Конструкторы подобъектов базовых классов вызываются конструктором производного класса: аргументы для этих конструкторов можно указать в списке инициализаторов членов.
Виртуальные базовые классы
Для каждого отдельного базового класса, указанного как virtual, наиболее производный объект содержит только один подобъект базового класса этого типа, даже если класс встречается в иерархии наследования несколько раз (при условии, что при каждом наследовании он наследуется как virtual).
struct B { int n; };
class X : public virtual B {};
class Y : virtual public B {};
class Z : public B {};
// every object of type AA has one X, one Y, one Z, and two B's:
// one that is the base of Z and one that is shared by X and Y
struct AA : X, Y, Z
{
AA()
{
X::n = 1; // modifies the virtual B subobject's member
Y::n = 2; // modifies the same virtual B subobject's member
Z::n = 3; // modifies the non-virtual B subobject's member
std::cout << X::n << Y::n << Z::n << '\n'; // prints 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() {} };
// the default constructor of AA calls the default constructors of X and Y
// but those constructors do not call the constructor of B because B is a virtual base
AA a; // a.n == 3
// the default constructor of X calls the constructor of B
X x; // x.n == 1
При виртуальном наследовании действуют особые правила поиска имён членов класса без уточнения (иногда называемые правилами доминирования).
Публичное наследование
Если класс использует public спецификатор доступа к членам для наследования от базового класса, все открытые члены базового класса доступны как открытые члены производного класса, а все защищённые члены базового класса доступны как защищённые члены производного класса (закрытые члены базового класса недоступны, если только для них не предоставлен доступ через дружественное объявление).
Публичное наследование моделирует отношение подтипов в объектно-ориентированном программировании: объект производного класса ЯВЛЯЕТСЯ объектом базового класса. Ожидается, что ссылки и указатели на производный объект будут пригодны для любого кода, ожидающего ссылки или указатели на любой из его открытых базовых классов (см. принцип подстановки Лисков) или, в терминах проектирования по контракту, производный класс должен сохранять инварианты своих открытых базовых классов, не должен усиливать предусловия или ослаблять постусловия любой функции-члена, которую он переопределяет.
#include <iostream>
#include <string>
#include <vector>
struct MenuOption { std::string title; };
// Menu is a vector of MenuOption: options can be inserted, removed, reordered...
// and has a title.
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';
}
};
// Note: Menu::title is not problematic because its role is independent of the base class.
enum class Color { WHITE, RED, BLUE, GREEN };
void apply_terminal_color(Color) { /* OS-specific */ }
// THIS IS BAD!
// ColorMenu is a Menu where every option has a custom color.
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 needs the following invariants that cannot be satisfied
// by publicly inheriting from Menu, for example:
// - ColorMenu::colors and Menu must have the same number of elements
// - To make sense, calling erase() should remove also elements from colors,
// in order to let options keep their colors
// Basically every non-const call to a std::vector method will break the invariant
// of the ColorMenu and will need fixing from the user by correctly managing colors.
int main()
{
ColorMenu color_menu;
// The big problem of this class is that we must keep ColorMenu::Color
// in sync with Menu.
color_menu.push_back(MenuOption{"Some choice"});
// color_menu.print(); // ERROR! colors[i] in print() is out of range
color_menu.colors.push_back(Color::RED);
color_menu.print(); // OK: colors and Menu has the same number of elements
}
Защищённое наследование
Если класс использует protected спецификатор доступа к членам для наследования от базового класса, все открытые и защищённые члены базового класса доступны как защищённые члены производного класса (закрытые члены базового класса недоступны, если только для них не предоставлен доступ через дружественное объявление).
Защищённое наследование можно использовать для «управляемого полиморфизма»: внутри членов Derived, а также внутри членов всех классов, производных от него, производный класс ЯВЛЯЕТСЯ базовым классом: ссылки и указатели на Derived можно использовать там, где ожидаются ссылки и указатели на Base.
Закрытое наследование
Если класс использует private спецификатор доступа к членам для наследования от базового класса, все открытые и защищённые члены базового класса доступны как закрытые члены производного класса (закрытые члены базового класса недоступны, если только для них не предоставлен доступ через дружественное объявление).
Закрытое наследование часто используется при проектировании на основе политик, поскольку политики обычно являются пустыми классами, а использование их в качестве базовых классов позволяет реализовать статический полиморфизм и воспользоваться оптимизацией пустого базового класса.
Закрытое наследование также можно использовать для реализации отношения композиции (подобъект базового класса является деталью реализации объекта производного класса). Использование члена обеспечивает лучшую инкапсуляцию и обычно предпочтительно, если только производному классу не требуется доступ к защищённым членам (включая конструкторы) базового класса, переопределение виртуального члена базового класса, конструирование базового класса до и разрушение после другого подобъекта базового класса, совместное использование виртуального базового класса или управление конструированием виртуального базового класса. Использование членов для реализации композиции также неприменимо в случае множественного наследования из пакета параметров или когда конкретные базовые классы определяются во время компиляции с помощью метапрограммирования шаблонов.
Подобно защищённому наследованию, закрытое наследование также можно использовать для управляемого полиморфизма: внутри членов производного класса (но не внутри классов, производных от него) производный класс ЯВЛЯЕТСЯ базовым классом.
template<typename Transport>
class service : private Transport // private inheritance from the Transport policy
{
public:
void transmit()
{
this->send(...); // send using whatever transport was supplied
}
};
// TCP transport policy
class tcp
{
public:
void send(...);
};
// UDP transport policy
class udp
{
public:
void send(...);
};
service<tcp> service(host, port);
service.transmit(...); // send over TCP
Поиск имён членов
Правила неквалифицированного и квалифицированного поиска имён для членов классов подробно описаны в разделе поиск имён.
Ключевые слова
Отчёты об ошибках
Следующие отчёты об ошибках, изменяющие поведение, были применены задним числом к ранее опубликованным стандартам C++.
| Отчёт об ошибке | Применяется к | Опубликованное поведение | Правильное поведение |
|---|---|---|---|
| CWG 1710 | C++98 | синтаксис class-or-decltype не позволял наследоваться от зависимого класса, для которого требуется уточнитель template | разрешён template |
См. также
© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/cpp/language/derived_class