Namespaces
Variants

Derived classes

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

Любой классовый тип (независимо от объявленного с помощью 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)
1) Определяет невиртуальное наследование с доступностью членов по умолчанию.
2) Определяет виртуальное наследование с доступностью членов по умолчанию.
3) Определяет невиртуальное наследование с заданной доступностью членов.
4) Определяет виртуальное наследование с заданной доступностью членов.
5) То же, что и 4), virtual и access-specifier могут появляться в любом порядке.
attr - (since C++11) последовательность любого количества атрибутов
access-specifier - один из private , public , или protected
class-or-computed - один из
  • nested-name-specifier  (опционально) type-name
  • nested-name-specifier template simple-template-id
(since C++11)
(since C++26)

Уточняющий спецификатор типа elaborated type specifier не может напрямую использоваться в качестве class-or-computed из-за синтаксических ограничений.

base-specifier s in a base-clause may be pack expansions .

A class or struct declared final cannot be denoted by class-or-computed .

(since C++11)
Перевод на русский язык:

base-specifier в base-clause могут быть pack expansions .

Класс или структура, объявленные final , не могут быть обозначены через class-or-computed .

(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 .

Ключевые слова

virtual

Отчеты о дефектах

Следующие отчеты об изменениях поведения, влияющие на дефекты, были применены задним числом к ранее опубликованным стандартам C++.

DR Applied to Behavior as published Correct behavior
CWG 1710 C++98 синтаксис class-or-decltype делал невозможным наследование от
зависимого класса, где требуется template дезинтегратор
разрешено template

Смотрите также