Короткий ответ: Наследование похоже на куклу Матрёшки, и каждый класс полностью содержит все базовые классы (если они есть).
Длинный ответ: Когда класс наследуется от одного или нескольких других классов, производный класс содержит его родительский класс (ы), который, в свою очередь, содержит их родительский класс (ы), пока вы не достигнете наименее производного класса (es) (класс (es), который не имеет собственных родительских классов). Так, например, с этой установкой:
class A {};
class B : public A {};
class C : public A {};
class D : public B, public C {};
class E : public D {};
E
содержит D
, который содержит B
(который содержит A
) и C
(который содержит другой A
); это выглядит примерно так (сгенерировано с помощью MSVC, используя compiler option /d1reportSingleClassLayoutE
в an online x64 environment).
class E size(1):
+---
| +--- (base class D)
| | +--- (base class B)
| | | +--- (base class A)
| | | +---
| | +---
| | +--- (base class C)
| | | +--- (base class A)
| | | +---
| | +---
| +---
+---
Обратите внимание, что эта аналогия немного не для virtual
базовых классов, которые имеют тенденцию быть расположены сразу после наиболее производного класса „основное тело“ (из-за отсутствия лучшего термина, память, выделенная для всех не - virtual
базовых классов и элементов данных) в памяти.
class A {};
class B : public virtual A {};
class C : public virtual A {};
class D : public B, public C {};
class E : public D {};
E
содержит D
, который содержит B
и C
. E
имеет один экземпляр A
, выведенный на спину.
class E size(16):
+---
| +--- (base class D)
| | +--- (base class B)
0 | | | {vbptr}
| | +---
| | +--- (base class C)
8 | | | {vbptr}
| | +---
| +---
+---
+--- (virtual base A)
+---
Поскольку каждый производный класс содержит всю свою иерархию наследования, он также содержит все переменные, объявленные в любом из его базовых классов.
class A { private: int a; protected: int b; public: int c; };
class B { public: int d; };
class C : public A, public B { protected: int e; };
class D : public C {};
static_assert(sizeof(C) == sizeof(A) + sizeof(B) + sizeof(int), "Size mismatch.");
static_assert(sizeof(D) == sizeof(C), "Size mismatch.");
static_assert(sizeof(D) == sizeof(int) * 5, "Size mismatch.");
D
содержит C
, которая содержит A
(который содержит 3 int
с), B
(который содержит int
), и int
. Ни Clang, GCC, ни MSVC не выдадут ошибку Size mismatch.
. Использование /d1reportSingleClassLayoutD
...
class D size(20):
+---
| +--- (base class C)
| | +--- (base class A)
0 | | | a
4 | | | b
8 | | | c
| | +---
| | +--- (base class B)
12 | | | d
| | +---
16 | | e
| +---
+---
Таким образом, спецификаторы доступа фактически не влияет на то, что это или не передается по наследству. Что они do влияют, однако, на то, что видно на производный класс.
private
участников видны только в классе, где они заявлены. a
видна в A
, но не в C
или D
protected
Участники видны по всей иерархии наследования после того, как они столкнулись. b
видна в A
, C
и D
(но не в B
, так как она не наследуется от A
). e
видна в C
и D
.
public
Участников обнажают, чтобы увидеть мир. c
и d
видны повсюду.
Все члены, объявленные в классе, могут видеть любой член, который видим их класс. Используя ваш пример, A::displayA()
всегда может видеть A::a
, даже если вызывается экземпляр производного класса B
; Однако, если B
заявляет член displayA()
, скрывающую A::displayA()
, то B::displayA()
не будет в состоянии видеть A::a
, и придется полагаться на public
или protected
членов A
, если он хочет работать с A::a
.
class A {
int a;
public:
void displayA() { std::cout << "A::a " << a << std::endl; }
};
class B : public A {
public:
// Will emit some variation on "A::a is private, you can't access it here."
// Note that no compiler will claim that it doesn't exist.
// void displayA() { std::cout << "A::a " << a << std::endl; }
// This works, though, since it goes through A::displayA(), which can see A::a.
void displayA() { return A::displayA(); }
};