Наследование: 'A' - недоступная основа 'B'

82
$ 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участников здесь.

  • Что я получаю об этой ошибке и что она означает?
  • По сути, что плохого в разрешении этого типа кода в C ++? Выглядит совершенно безобидно.
Лазер
источник

Ответы:

100

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

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

Частное наследование не обязательно (или даже обычно) должно следовать принципу замещения Лискова . Публичное наследование утверждает, что производный объект может быть заменен объектом базового класса, и правильная семантика все равно будет результатом. Однако частное наследование этого не утверждает. Обычное описание отношений, подразумеваемых частным наследованием, - «реализовано в терминах».

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

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

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

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

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

Джерри Гроб
источник
1
это также относится кprotected
SubMachine 08
12

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

Оно делает. И если

A* p = new B;

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

Бен Фойгт
источник
8

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.

Я не специалист по C ++, но, похоже, это просто недопустимо. Пойду покопаюсь в спецификации и посмотрю, что у меня получится.

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

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

Карл Норум
источник
5

Это довольно просто: тот факт, что Aнаследуется частным образом, означает, что факт Bрасширения Aявляется секретом и только B«знает» его. Это само определение частного наследования.

Эрнест Фридман-Хилл
источник
4
Но я получаю ту же ошибку, если заменяю privateна protected.
Lazer
2
Конечно. «Защищено» означает, что знания ограничены Bи подклассами (и друзьями) B. " A* ab = new B;" будет законным в гипотетическом классе C, являющемся подклассом B.
Эрнест Фридман-Хилл,
3

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

tmpearce
источник
Спасибо, но это как-то не имеет смысла. privateЕдинственное дело должно заключаться в том, чтобы контролировать поведение членов. Какой будет вред, если информация о наследовании не будет скрыта?
Lazer
1
Частное наследование - это форма агрегирования / композиции. Это способ иметь свойства базового класса, не являясь объектом базового класса. Если это не то, что вы хотите, частное наследование не для вас. Вот как это работает.
tmpearce
0

Это работает

#include <iostream>

using namespace std;

class A{
    public:
        virtual void update() = 0;
};

class B: public A{
    public:
    virtual void update(){std::cout<<"hello";};
};

int main()
{
    A *a = new B();

    a->update();

    return 0;
}
Дениз Бабат
источник