Short answer: Inheritance is like a Matryoshka doll, and every class fully contains all of its base classes (if any).
Long answer: When a class inherits from one or more other classes, the derived class contains its parent class(es), which in turn contain their parent class(es), until you reach the least-derived class(es) (the class(es) which have no parent classes of their own). So, for example, with this setup:
class A {};
class B : public A {};
class C : public A {};
class D : public B, public C {};
class E : public D {};
E
contains a D
, which contains a B
(which contains an A
) and a C
(which contains another A
); it looks something like this (generated with MSVC, using compiler option /d1reportSingleClassLayoutE
in an online x64 environment).
class E size(1):
+---
| +--- (base class D)
| | +--- (base class B)
| | | +--- (base class A)
| | | +---
| | +---
| | +--- (base class C)
| | | +--- (base class A)
| | | +---
| | +---
| +---
+---
Note that this analogy is slightly off for virtual
base classes, which tend to be located immediately after the most-derived class' "main body" (for lack of a better term; the memory allocated to all non-virtual
base classes and data members) in memory.
class A {};
class B : public virtual A {};
class C : public virtual A {};
class D : public B, public C {};
class E : public D {};
E
contains a D
, which contains a B
and a C
. E
has a single instance of A
superglued to its back.
class E size(16):
+---
| +--- (base class D)
| | +--- (base class B)
0 | | | {vbptr}
| | +---
| | +--- (base class C)
8 | | | {vbptr}
| | +---
| +---
+---
+--- (virtual base A)
+---
As each derived class contains its entire inheritance hierarchy, it also contains all variables declared in any of its base classes.
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
contains C
, which contains A
(which contains 3 int
s), B
(which contains an int
), and an int
. Neither Clang, GCC, or MSVC will emit a Size mismatch.
error. Using /d1reportSingleClassLayoutD
...
class D size(20):
+---
| +--- (base class C)
| | +--- (base class A)
0 | | | a
4 | | | b
8 | | | c
| | +---
| | +--- (base class B)
12 | | | d
| | +---
16 | | e
| +---
+---
As such, access specifiers don't actually affect what is or isn't inherited. What they do affect, however, is what's visible to the derived class.
private
members are only visible in the class where they're declared. a
is visible in A
, but not in C
or D
protected
members are visible throughout the inheritance hierarchy, after being encountered. b
is visible in A
, C
, and D
(but not in B
, since it doesn't inherit from A
). e
is visible in C
and D
.
public
members are laid bare for the world to see. c
and d
are visible everywhere.
All members declared in a class can see any member that is visible to their containing class. Using your example, A::displayA()
can always see A::a
, even when called on an instance of derived class B
; however, if B
declares a member displayA()
that hides A::displayA()
, then B::displayA()
won't be able to see A::a
, and would have to rely on public
or protected
members of A
if it wanted to work with 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(); }
};