Спадщина: "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, ми не хочемо цього щоб вплинути на будь-який клієнтський код - для клієнтського коду це повинен бути лише стек, а не якийсь спеціалізований різновид векторів (або deque).


1
це також стосуєтьсяprotected
SubMachine

12

приватне успадкування має змінювати лише те, як члени класу В будуть видимими для зовнішнього світу

Це робить. І якщо

A* p = new B;

було дозволено, тоді успадковані члени будь-кого 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:

Перше значення типу "вказівник на cv D ", де Dє типом класу, може бути перетворено на перше значення типу "вказівник на cv B", де B - базовий клас D. Якщо Bце недоступний або неоднозначний базовий клас D, програма, яка вимагає цього перетворення, неправильно сформована.


5

Це досить просто: той факт, що Aуспадковується приватно, означає, що той факт, що Bпоширюється, Aє таємницею, і це лише B"знає". Це саме визначення приватної спадщини.


4
Але я отримую ту саму помилку, якщо заміню privateна protected.
Лазер

2
Справді. "Захищений" означає, що знання обмежені Bта підкласами (і друзями) B. " A* ab = new B;" було б законним у гіпотетичному класі, Cякий був підкласом B.
Ернест Фрідман-Хілл

3

Приватне успадкування означає, що поза похідним класом інформація про успадкування прихована. Це означає, що ви не можете передати похідний клас до базового класу: зв’язок не відомий абоненту.


Дякую, але це якось не має сенсу. privateЄдиним бізнесом має бути контроль за поведінкою членів. Якою буде шкода, якщо інформація про спадщину не буде прихована?
Лазер

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;
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.