Spec-Zone.ru › C++

Производные классы

Любой тип класса (будь то объявленный с помощью ключевого слова class class или struct) может быть объявлен как производный от одного или нескольких базовых классов, которые, в свою очередь, могут быть производными от своих собственных базовых классов, образуя иерархию наследования.

Синтаксис

Список базовых классов указан в описании базовых классов синтаксиса объявления класса. Описание базовых классов состоит из символа : и за ним следует список одного или нескольких спецификаторов базового класса, разделённых запятыми.

атрибут (необязательно) класс или вычисляемый тип (1)
атрибут (необязательно) virtual класс или вычисляемый тип (2)
атрибут (необязательно) спецификатор доступа класс или вычисляемый тип (3)
атрибут (необязательно) virtual спецификатор доступа класс или вычисляемый тип (4)
атрибут (необязательно) спецификатор доступа virtual класс или вычисляемый тип (5)
1) Указывает невиртуальное наследование с доступностью членов по умолчанию.
2) Указывает виртуальное наследование с доступностью членов по умолчанию.
3) Указывает невиртуальное наследование с заданной доступностью членов.
4) Указывает виртуальное наследование с заданной доступностью членов.
5) Аналогично 4), virtual и спецификатор доступа могут быть указаны в любом порядке.
атрибут - (с C++11) последовательность любого числа атрибутов
спецификатор доступа - один из private, public, или protected
класс или вычисляемый тип - один из
  • спецификатор имени вложенного класса (необязательно) имя типа
  • спецификатор имени вложенного класса template идентификатор шаблона простого типа
  • спецификатор decltype
(с C++11)
  • спецификатор индекса пакета
(с C++26)

Спецификатор типа с расширенной записью не может непосредственно выступать в роли класс или вычисляемый тип из-за ограничений синтаксиса.

спецификаторы базового класса в описании базовых классов могут быть расширениями пакетов параметров.

Класс или структура, объявленная как final, не может быть обозначена как класс или вычисляемый тип.

(с C++11)

Если спецификатор доступа опущен, он по умолчанию равен public для производных классов, объявленных с ключевым словом class struct и private для производных классов, объявленных с ключевым словом class class.

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;
};

Классы, обозначенные класс или вычисляемый тип, перечисленные в описании базовых классов, являются прямыми базовыми классами. Их базовые классы являются косвенными базовыми классами. Один и тот же класс не может быть указан как прямой базовый класс более одного раза, но один и тот же класс может быть как прямым, так и косвенным базовым классом.

Каждый прямой и косвенный базовый класс присутствует в виде субобъекта базового класса внутри представления объекта производного класса по смещению, зависящему от 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
    }
};

Пример иерархии наследования с виртуальными базовыми классами — это иерархия iostreams стандартной библиотеки: 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

Когда класс использует public спецификатор доступа к членам для наследования от базового класса, все публичные члены базового класса доступны как публичные члены производного класса, а все защищённые члены базового класса доступны как защищённые члены производного класса (приватные члены базового класса никогда не доступны, если они не дружественные).

Наследование с доступностью public моделирует отношение подтипизации в объектно-ориентированном программировании: объект производного класса ЕСТЬ-ОБЪЕКТ базового класса. Ссылка или указатель на производный объект должны быть работоспособны в любом коде, ожидающем ссылки или указатели на любой из его публичных базовых классов (см. LSP) или, в терминах DbC, производный класс должен поддерживать инварианты класса своих публичных базовых классов, не должен усиливать никакие предпосылки или ослаблять никакие пост-условия функции члена, которую он переопределяет.

#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

Когда класс использует protected спецификатор доступа к членам для наследования от базового класса, все публичные и защищённые члены базового класса доступны как защищённые члены производного класса (приватные члены базового класса никогда не доступны, если они не дружественные).

Наследование с доступностью protected может использоваться для "управляемой полиморфности": в членах производного класса, а также в членах всех производных классов, производный класс ЕСТЬ-ОБЪЕКТ базового класса: ссылки и указатели на производный класс могут использоваться там, где ожидаются ссылки и указатели на базовый класс.

Наследование с доступностью private

Когда класс использует private спецификатор доступа к членам для наследования от базового класса, все публичные и защищённые члены базового класса доступны как приватные члены производного класса (приватные члены базового класса никогда не доступны, если они не дружественные).

Наследование с доступностью private часто используется в политико-ориентированном проектировании, так как политики обычно представляют собой пустые классы, и их использование в качестве базовых классов позволяет использовать как статический, так и динамический полиморфизм, а также использовать оптимизацию пустых базовых классов.

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

Подобно наследованию с доступностью protected, наследование с доступностью 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++.

DR Применимо к Поведение, опубликованное Правильное поведение
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/w/cpp/language/derived_class

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API