Наследование: "A" является недоступной базой "B"

$ cat inheritance.cpp 
#include <iostream>

using namespace std;

class A { };
class B : private A { };

int main() {
    A* ab = new B;
}
$
$ g++ inheritance.cpp
inheritance.cpp: In function 'int main()':
inheritance.cpp:9: error: 'A' is an inaccessible base of 'B'
$

Я просто не понимаю эту ошибку.

Как я понимаю, и поскольку этот учебник подтверждает, private наследование должно только изменять, как члены class B видны внешнему миру.

Я думаю, что частный спецификатор делает больше, чем просто меняет видимость членов class B.

  • Что я получу эту ошибку и что это значит?
  • В принципе, что не так, если разрешить этот тип кода на С++? Выглядит совершенно безвредно.

Ответ 1

Сделав наследование приватным, вы в основном говорите, что даже тот факт, что B наследует от A (вообще), является закрытым - недоступным/видимым для внешнего мира.

Не вдаваясь в длинное обсуждение того, что произойдет, если это будет разрешено, простой факт заключается в том, что он не допускается. Если вы хотите использовать указатель на базу для ссылки на объект производного типа, то вы в значительной степени застреваете с использованием общего наследования.

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

Основная проблема заключается в том, что частное наследование не обязательно должно следовать принципу подстановки Лискова. Открытое наследование утверждает, что производный объект может быть заменен объектом базового класса, и правильная семантика будет по-прежнему возникать. Однако частное наследование не утверждает. Обычное описание отношения, подразумеваемого частным наследованием, "реализуется в терминах".

Открытое наследование означает, что производный класс поддерживает все возможности базового класса и потенциально дополнительно добавляет. Частное наследование часто означает более или менее противоположное: производный класс использует общий базовый класс для реализации чего-то с более ограниченным интерфейсом.

Например, допустим, что контейнеры в стандартной библиотеке С++ были реализованы с использованием наследования, а не шаблонов. В текущей системе std::deque и std::vector являются контейнерами, а std::stack - контейнерным адаптером, который обеспечивает более ограниченный интерфейс. Поскольку он основан на шаблонах, вы можете использовать std::stack в качестве адаптера для std::deque или std::vector.

Если бы мы хотели обеспечить по сути то же самое с наследованием, мы, вероятно, использовали бы частное наследование, поэтому std::stack будет выглядеть примерно так:

class stack : private vector {
    // ...
};

В этом случае мы определенно не хотим, чтобы пользователь мог управлять нашим stack, как если бы это был vector. Это может (и, скорее всего, будет) нарушать ожидания стека (например, пользователь мог бы вставлять/удалять элементы в середине, а не просто в виде стека, как предполагалось). В основном мы используем vector как удобный способ для реализации нашего стека, но если (например) мы изменили реализацию для stack автономно (без зависимости от базового класса) или повторно внедрить ее в терминах std::deque, мы не хотим, чтобы это повлияло на какой-либо клиентский код - на клиентский код, это должен быть всего лишь стек, а не какой-либо специализированный вариант векторного (или deque).

Ответ 2

частное наследование должно изменять только то, как члены класса B видны внешнему миру

Это так. И если

A* p = new B;

то унаследованные члены любого B могут быть доступны из внешнего мира, просто создав A*. Поскольку они приватно унаследованы, этот доступ является незаконным, и так же происходит переполнение.

Ответ 3

clang++ дает несколько более понятное сообщение об ошибке:

example.cpp:9:13: error: cannot cast 'B' to its private base class 'A'
    A* ab = new B;
            ^
example.cpp:6:11: note: declared private here
class B : private A { };
          ^~~~~~~~~
1 error generated.

Я не эксперт на С++, но похоже, что это просто не разрешено. Я поеду вокруг спецификации и посмотрю, что я придумал.

Изменить: здесь соответствующую ссылку из спецификации - 4.10 Преобразование указателей, пункт 3:

Значение типа "указатель на cv D", где D - тип класса, может быть преобразовано в prvalue типа "указатель на cv B", где B является базовым классом D. Если B является недоступным или двусмысленным базовым классом D, программа, которая требует этого преобразования, плохо сформирована.

Ответ 4

Это довольно просто: тот факт, что A наследуется конфиденциально, означает, что факт, что B extends A является секретом, и только B "знает" его. Это само определение частного наследования.

Ответ 5

Частное наследование означает, что вне производного класса информация о наследовании скрыта. Это означает, что вы не можете отнести производный класс к базовому классу: отношение неизвестно вызывающему.