C++ (Multiple) Inheritance
- All members are inherited to the derived class.
- But the private member of the base class is not directly accessible.
- The inherited private member of a base class can be accessed via inherited public member function. In other words, it has to work through the base-class method.
- Public member of a base class inherited as a public member.
- Make the constructor of the base class private.
class A { public: static A* create() {return new A();} private: A() {}; }; class B : public A {}; B b; // Error : cannot access to // the parent's private constructor A()
This works. However, user should always call A::create() static function, and the object cannot be created on the stack. So, if we do not want that happening, we may opt to use the 2nd way as described below. - Use virtual inheritance.
class B; class A { private: A() {}; friend class B; }; class B : virtual public A {}; class Derived : public B {}; // Error : cannot access to A()
It's utilizing the following requirement:
A derived class (Derived class in the example) with an indirect virtual base class (A in the example) should have its constructors (Derived::Derived()) invoke the indirect base class constructors (A::A()) directly, which is illegal for indirect nontrivial base classes.
Normally, constructor A() would have been called from the constructor of the class B if there had been no virtual inheritance. It can be checked with the following code:#include <iostream> using namespace std; class B; class A { private: A() {cout << "A()" << endl;}; friend class B; }; // B can access A() because of the "friend" class B : public A {public: B(){ cout << "B()" << endl;}}; class Derived : public B {public: Derived() {cout << "Derived()";}}; int main() { Derived dd; return 0; }
The output from the code:A() B() Derived()
In other words, because of the virtual, the indirect (with respect to the for the Derived class) constructor A() is called from Derived::Derived() not from B::B(). But it's not accessible from Derived class due to the private access.
The code below shows only public members are accessible from outside of the class:
class A { public: int xPublic; protected: int xProtected; private: int xPrivate; }; class B : public A {}; int main(int argc, char** argv) { A a; a.xPublic = 0; a.xProtected = 0; // error: inaccessible a.xPrivate = 0; // error: inaccessible B b; b.xPublic = 0; b.xProtected = 0; // error: inaccessible b.xPrivate = 0; // error: inaccessible return 0; }
The following code shows that the inherited member is accessible within the class. But still even the inherited member accessing outside a class is not allowed (b.xProtected) because the same rule applies to the inherited member: protected member cannot be accessed from outside of a class (inherited member of a parent class remains as a protected member of a child class).
class A { public: int xPublic; protected: int xProtected; private: int xPrivate; }; class B : public A { public: void foo(A *a, B *b) { a->xProtected = 0.0; // error: A::xProtected inaccessible b->xProtected = 0.0; // OK: inherited member this->xProtected = 0.0; } } int main(int argc, char** argv) { A a; B b; b.xProtected = 0.0; // error: inaccessible outside of a class - inherited xProtected b.foo(&a;, &b;); return 0; }
(Note) Non-static protected members of a base class can not be accessed via a pointer or reference to the base class.
The accessibility property of the introduced foo() method from the A type is private in the C type, hence it will not be publicly accessible. This is the case regardless of the fact that the A type is publicly derived and the foo method has public accessibility in A type. The alias created by the using declaration has the usual accessibility for a member declaration.
#include <iostream> class A { public: void foo() const { std::cout << "A"; } }; class B { public: void foo() const { std::cout << "B"; } }; class C : public A, public B { using A::foo; // note that this is private section }; int main() { B b; b.foo(); // OK: B::foo() C c; c.foo(); // error: A::foo() or private member in class 'C' return 0; }
A derived class does not have direct access to the inherited private member of a base class. A derived class can access to it via inherited public member function. In other words, it has to work through the base-class method.
This applies to the constructor as well. When we construct a derived class object, the base object must be created first. If we do not specify any base-constructor, it calls a base-constructor. This is because the base-constructor does initialization of derived object's the inherited base-class member. The members of derived object are initialized by the derived-constructor.
Remember, a derived-class constructor always calls a base-class constructor.
#include <iostream> using namespace std; class A { public: A(int n = 1) : ia(n) { cout << "A()" << endl; } protected: int ia; }; class B : public A { public: B(int n) : ib(n) { cout << "B()" << endl; } private: int ib; }; int main() { B b(2); return 0; }
Output:
A() B()
As we see from the result, the derived-class constructor calls the base-class constructor. Actually, the base-class object should be constructed before the code enters the body of the derived-class constructor.
If we need to initialize inherited the base-class member with different value form a default value, we can use base-class constructor in the initializer list of the derived-class constructor. Let's say, if we want to set the inherited member as 12 not the default value of 1. Then, we use the following line of code for the initializer.
#include <iostream> using namespace std; class A { public: A(int n = 1) : ia(n) { cout << "A() ia = " << ia << endl; } protected: int ia; }; class B : public A { public: B(int n) : ib(n), A(n+10) { cout << "B()" << endl; } private: int ib; }; int main() { B b(2); return 0; }
Output:
A() ia = 12 B()
In the following code, we have two classes A and B. The B is a derived class from the class A. In the constructor of B, we do not specify the A-class constructor to use. In the class B, we have two objects of type A as it's member. Here is the code:
#include <iostream> using namespace std; class A { public: A(int n = 1) : ia(n) { cout << "A() ia = " << ia << endl; } ~A() { cout << "~A() ia = " << ia << endl; } protected: int ia; }; class B : public A { public: B(int n) : a1(ia+10), a2(n) { cout << "B()" << endl; } ~B() { cout << "~B() ia = " << ia-- << endl; } private: A a2; A a1; }; int main() { {B b(20);} return 0; }
Output:
A() ia = 1 A() ia = 20 A() ia = 11 B() ~B() ia = 1 ~A() ia = 11 ~A() ia = 20 ~A() ia = 0
Let's review the output.
First, the class B's constructor called the base-class A()'s default constructor which initialized ia as 1. Then we initialized the two A objects: a2 with n(=20) and a1 with ai+10(=1+10). Note that member initialization was done in the order of the appearance in the class definition but not in the order of the initializer list.
When object b was out of {} block scope, the destructor of B called. As we see in the output, the value of the inherited member ia is 1 because it was constructed using default value in the A constructor. Then, the two A object members of B object called two destructors with ia values of 11 and 20. Finally, the base-object destructor called with ia = 0 since it was decremented by the B's destructor.
The following code is very similar to the previous example except the class B has a member of pointer to an array of type A object.
#include <iostream> using namespace std; class A { public: A(int n = 1) : ia(n) { cout << "A() ia = " << ia << endl; } ~A() { cout << "~A() ia = " << ia << endl; } protected: int ia; }; class B : public A { public: B(int n) : a1(new A[5]), a2(n) { cout << "B()" << endl; } ~B() { cout << "~B() ia = " << ia-- << endl; delete[] a1; } private: A a2; A* a1; }; int main() { {B b(20);} return 0; }
Output:
A() ia = 1 A() ia = 20 A() ia = 1 A() ia = 1 A() ia = 1 A() ia = 1 A() ia = 1 B() ~B() ia = 1 ~A() ia = 1 ~A() ia = 1 ~A() ia = 1 ~A() ia = 1 ~A() ia = 1 ~A() ia = 20 ~A() ia = 0
As we can see from the output, we call default constructor of A five more times because of the array A[5] in the initializer of B.
In Java, there is a feature called final class which prohibits users from inheriting derived class from the final class. But not in C++.
However, we can achieve it in two ways:
Constructing an object of derived type involves constructing and initializing all the base subobjects. The base class constructors are invoked in the order in which they appear in the class derivation list. Given the class hierarchy in the code below, let's guess in what order the base constructors are constructed:
#include <iostream> using namespace std; class X { public: X(){cout << "X() ";} ~X(){cout << "~X() ";} }; class Y { public: Y(){cout << "Y() ";} ~Y(){cout << "~Y() ";} }; class Z : public X, public Y { public: Z(){cout << "Z() ";} ~Z(){cout << "~Z() ";} }; class A { public: A(){cout << "A() ";} ~A(){cout << "~A() ";} }; class B : public A { public: B(){cout << "B() ";} ~B(){cout << "~B() ";} }; class C : public B { public: C(){cout << "C() ";} ~C(){cout << "~C() ";} }; class F : public C, public Z { public: F(){cout << "F() ";} ~F(){cout << "~F() ";} }; int main() { {F f;} return 0; }
Output:
A() B() C() X() Y() Z() F() ~F() ~Z() ~Y() ~X() ~C() ~B() ~A()
As we see from the output, the order of destruction is the reverse of the constructor calls.
Virtual base classes allow an object derived from multiple bases that themselves share a common base to inherit just one object of the shared base class.
class Car {}; class NormalCar : virtual public Car {}; class ElectricCar : public virtual Car {}; class HybridCar : public NormalCar, public ElectricCar {};
Now a HybridCar object will contain a single copy of a Car object. Actually, the inherited NormalCar and ElectricCar objects share a common Car object instead of each bringing in its own copy so that HybridCar object now contains one Car object.
Virtual base classes force us to take a new approach to class constructors. In normal cases where we do not have virtual base classes, the only constructor that can appear in an initialization list are the constructors for immediate base classes. These constructors can, in turn, pass information onto their bases as shown in the example below:
#include <iostream> using namespace std; class X { public: X(int nx = 0):x(nx) {} int x; }; class Y : public X { public: Y(int ny = 0, int nx = 0): X(nx), y(ny){} int y; }; class Z : public Y { public: Z(int nz = 0, int ny = 0, int nx = 0): Y(ny, nx), z(nz){} int z; }; int main() { Z zObj(1, 2, 3); cout << zObj.x << zObj.y << zObj.z << endl; //321 return 0; }
A Z constructor can invoke only constructors from the Y, and a Y constructor can invoke only constructors from the X class. In the code, the Z constructor uses nz value and then passes the values of ny and nx back to the Y constructor. The Y constructor, then, uses the value of ny, and passes nx back to the X constructor. However, this automatic passing of information does not work if the Car is a virtual base class. So, the following code won't work, and may get similar error to the following complain from the compiler:
'Car::Car' : no appropriate default constructor available
Here is the problematic code regarding the constructor invoke when we have virtual base class:
#include <iostream> #include <string> using namespace std; class Car { public: Car(string s):name(s){} string name; }; class NormalCar : virtual public Car { public: NormalCar(int d, string s=""): Car(s), displacement(d){} int displacement; }; class ElectricCar : public virtual Car { public: ElectricCar(int bl, string s = ""): Car(s), batteryLife(bl){} int batteryLife; }; class HybridCar : public NormalCar, public ElectricCar { public: HybridCar(int p = 50000, int b = 1, int d = 2000, string s="") : NormalCar(d, s), ElectricCar(b, s), price(p) {} int price; }; int main() { HybridCar h(65000, 2, 2500, "mcar"); return 0; }
The problem is that automatic passing of information would pass s to the Car object via two separate paths (NormalCar and ElectricCar), and it's ambiguous. So, C++ disables the automatic passing through an intermedia class to a base class if the base class is virtual. Therefore, we need to use Car constructor directly in the initializer of the HybridCar as shown in the example below:
#include#include using namespace std; class Car { public: Car(string s):name(s){} string name; }; class NormalCar : virtual public Car { public: NormalCar(int d, string s=""): Car(s), displacement(d){} int displacement; }; class ElectricCar : public virtual Car { public: ElectricCar(int bl, string s = ""): Car(s), batteryLife(bl){} int batteryLife; }; class HybridCar : public NormalCar, public ElectricCar { public: HybridCar(int p = 50000, int b = 1, int d = 2000, string s="") : NormalCar(d, s), ElectricCar(b, s), Car(s), price(p) {} int price; }; int main() { HybridCar h(65000, 2, 2500, "mcar"); cout << "name: " << h.name << endl; cout << "displacement: " << h.displacement << endl; cout << "batterLife: " << h.batteryLife << endl; cout << "price: " << h.price << endl; return 0; }
Output:
name: mcar displacement: 2500 batterLife: 2 price: 65000
Ph.D. / Golden Gate Ave, San Francisco / Seoul National Univ / Carnegie Mellon / UC Berkeley / DevOps / Deep Learning / Visualization