Chapter 3-3.15 of learning notes of C + + new classic course

  This blog will record the notes of section 15 of the knowledge points of the new classic course!

The knowledge points in this section are inherited constructor, multiple inheritance and virtual inheritance.

The knowledge summarized today is divided into the following five points:

(1) Inherited constructor
(2) Multiple inheritance
    (2.1) overview of multiple inheritance
    (2.2) static member variables
    (2.3) derived class constructors and destructors
    (2.4) inherit constructors from multiple parent classes
(3) Type conversion
(4) Virtual base class, virtual inheritance (virtual derivation)
(5) Summary

(1) Inherited constructor:

(I think it's better to use it less, because it will hide the detailed implementation of the constructor of the subclass, which is not conducive to maintaining the code in the future! In order to prevent yourself from reading the code written by others in the future, you can understand it.)

        When inheriting, a subclass can only inherit the constructor of its direct base class (parent class). The default (that is, the compiler automatically generates it for us), copy and move constructors cannot be inherited. So how to write an inherited constructor?

         Format: using class name: class name (constructor name);

         be careful:

         ① If the constructor of the base class has a default parameter value, the compiler will encounter using A::A; It will help us construct multiple constructors in subclasses:

The specific construction rules are as follows:

         a) Constructor with all parameter

         b) For the remaining constructors, each omits a default parameter.  

For example:

class A {
public:
    int a1, a2, a3;
    A(int i, int j, int k = 5):a1(i),a2(j),a3(k) {}
};
class B :public A {
public:
    using A::A;//Inherit the constructor of A. The using keyword means to make a variable / function visible in the current scope.
               //When this code is encountered, the compiler will use each constructor in the base class to generate a constructor for its corresponding subclass
               //==>B (constructor parameter list): a (constructor parameter list of a) {}
               //==> B(int i,int j,int k):A(i,j,k){}
    //And because there are default parameters, so
    //Using a:: a; = = >
    //B(int i, int j, int k) :A(i, j, k) {}
    //B(int i,int j) :A(i,j){}

};

         ② If the subclass contains only using class name:: class name; When inheriting the constructor, the compiler does not think that we have defined the constructor for the subclass. Therefore, the compiler will also automatically generate the default constructor of subclasses for us.

(2) Multiple inheritance:

    (2.1) overview of multiple inheritance:

        Multiple inheritance: as the name suggests, it means that a subclass inherits from multiple (2 or more) parents (a child has many fathers).

class GrandFather {
public:
    int m_Age;
    string m_Name;
    GrandFather(int age,string name):m_Age(age),m_Name(name){}
    virtual void showInfo() {
        cout << "GrandFather's Name: " << this->m_Name << endl;
        cout << "GrandFather's Age: " << this->m_Age << endl;
    }
    virtual ~GrandFather() {}
};
class A :public GrandFather {//Class A public inherits from class GrandFather
public:
    A(int age, string name) :GrandFather(age, name) {}
};
class B{//Class B is a separate class
public:
    int m_b;
    B(int mb):m_b(mb) {}
    virtual ~B() {}
};
class C:public A,public B {//Class C is publicly inherited from classes A and B
public:
    C(int age, string name,int mb) :A(age, name),B(mb) {}
    virtual void showInfo() {
        cout << "C's Name: " << this->m_Name << endl;
        cout << "C's Age: " << this->m_Age << endl;
        cout << "C's mb: " << this->m_b << endl;
    }
    virtual ~C() {}
};

         In order to make you more aware of the result relationship of multi inheritance, I draw a schematic diagram here:

     (2.2) static member variables:

         Declare a static member variable in the GrandFather class and define it outside the class:

class GrandFather {
public:
    int m_Age;
    string m_Name;
    GrandFather(int age,string name):m_Age(age),m_Name(name){}
    virtual void showInfo() {
        cout << "GrandFather's Name: " << this->m_Name << endl;
        cout << "GrandFather's Age: " << this->m_Age << endl;
    }
    virtual ~GrandFather() {}
public:
    static int grandStaticVar;//Declare static member variables within a class
};
int GrandFather::grandStaticVar = 100;//Define a static member variable outside the class (that is, allocate memory to the static member variable)
//If you don't need to use the static member variable in your subsequent code, you don't need to define the static member variable anymore!
//If you use this variable in subsequent code, you must define the static member variable outside the class, otherwise you will make a link error!
//In main:
multiSucc::C cc(123, "lyf",11);
cout << multiSucc::GrandFather::grandStaticVar <<"\t"// == 100
<< multiSucc::A::grandStaticVar << "\t"// == 100
<< multiSucc::C::grandStaticVar << "\t"// == 100
<< cc.grandStaticVar << endl;// == 100
    
cc.grandStaticVar = 110;

cout << multiSucc::GrandFather::grandStaticVar << "\t"// == 110
    << multiSucc::A::grandStaticVar << "\t"// == 110
    << multiSucc::C::grandStaticVar << "\t"// == 110
    << cc.grandStaticVar << endl;// == 110

Operation results:

     (2.3) derived class constructors and destructors:

a) Constructing a derived class object will construct and initialize all the components of the direct base class at the same time.

b) The constructor initialization list of a derived class only initializes its direct base class, and it is no exception in multi inheritance. In this way, the parameters will be passed back to the original base class layer by layer, and the base class components of all will be constructed.

c) The construction order of the base class is consistent with that of the base class in the derived list! The destructional order is opposite.

d) The destructor of each class will only do the release of its own components (the destructors between classes do not interfere with each other).

class GrandFather {
public:
    int m_Age;
    string m_Name;
    GrandFather(int age,string name):m_Age(age),m_Name(name){
        cout << "GrandFather Constructor execution!" << endl;
    }
    virtual void showInfo() {
        cout << "GrandFather's Name: " << this->m_Name << endl;
        cout << "GrandFather's Age: " << this->m_Age << endl;
    }
    virtual ~GrandFather() {
        cout << "GrandFather Destructor execution!" << endl;
    }
};
class A :public GrandFather {
public:
    A(int age, string name) :GrandFather(age, name) {
        cout << "A Constructor execution!" << endl;
    }
    virtual ~A() {
        cout << "A Destructor execution!" << endl;
    }
};
class B{
public:
    int m_b;
    B(int mb):m_b(mb) {
        cout << "B Constructor execution!" << endl;    
    }
    virtual ~B() {
        cout << "B Destructor execution!" << endl;
    }
};
class C:public A,public B {//This line is the so-called "derived list"
public:
    C(int age, string name,int mb) :A(age, name),B(mb) {
        cout << "C Constructor execution!" << endl;
    }
    virtual void showInfo() {
        cout << "C's Name: " << this->m_Name << endl;
        cout << "C's Age: " << this->m_Age << endl;
        cout << "C's mb: " << this->m_b << endl;
    }
    virtual ~C() {
        cout << "C Destructor execution!" << endl;
    }
};
//In main:
multiSucc::C cc(123, "lyf", 11);

Operation results:

  If you modify the derived list to class C:public B,public A   Then the operation result is:

Supplement: here is a supplementary problem of implicitly initializing the base class.

stay B Class
B() {
    cout << "B Default constructor execution!" << endl;
}
take C The constructor in the class is modified to:
C(int age, string name, int mb) :A(age, name) {
    cout << "C Constructor execution!" << endl;
}

Operation results:

         Here, the constructor of class B is not explicitly called in the constructor of class C, but the compiler will implicitly initialize the default constructor of class B.

    (2.4) inherit constructors from multiple parent classes:

        When inheriting constructors from multiple parent classes, if the constructors inherited from using are the same (that is, the parameters are the same, and the function body implements the same constructor), it is wrong to use the syntax!

        In this case, you can only customize a new constructor belonging to this class for this subclass, but you can't use using to help you save the workload of defining constructors!

class A {
public:
    A(int tv) {}
};
class B {
public:
    B(int tv) {}
};
class C : public A,public B {
public:
    using A::A;//Constructor inheriting class A = = > C (int TV): A (TV) {}
    using B::B;//Wrong! The constructor as like as two peas to inherit B class is = = > C (int TV): B (TV) {}, which is exactly the same as inherited from class A.
};

Operation results:

  The constructor of subclass C that inherits from parent A and parent B has been redefined. Therefore, we can only define A constructor belonging to subclass C:

class A {
public:
    A(int tv) {}
};
class B {
public:
    B(int tv) {}
};
class C : public A,public B {
public:
    using A::A;//Constructor inheriting class A = = > C (int TV): A (TV) {}
    using B::B;//Constructor inheriting class B = = > C (int TV): B (TV) {}
    C(int tv):A(tv),B(tv){
        cout << "C Class constructor executed!" << endl;
    }
};
//In main:
C ctest(888);

Operation results:

  (3) Type conversion:

         In the previous single inheritance, we learned that the base class pointer can point to a derived class (subclass) object: because the compiler will help us implicitly perform this derived class to base class conversion. The reason: because each derived class object will contain the components of the base class object.

        In multi inheritance, the base class pointer can also point to a derived class (subclass) object!

         Therefore, the following formulas are correct:

//Take the code in section (2) above as an example:
using namespace multiSucc;
GrandFather* pg = new C(23,"lzf",23);
A* pa = new C(23, "lzf", 23);
B* pb = new C(23, "lzf", 23);
C myc(23, "lzf", 23);
GrandFather g(myc);

         If you have any doubts about the above formula, you can refer to my blog summary in section 3.11, which will not be repeated here!  

(4) Virtual base class, virtual inheritance (virtual derivation):

         In the derived list, the same base class can only appear once, with the following two special exceptions:

         a) A derived class can inherit from the same indirect base class through its two direct base classes.

  See the following code:

class GrandFather {
public:
    GrandFather(int age):m_Age(age){ cout << "GrandFather Constructor execution!" << endl; }
    virtual ~GrandFather() {
        cout << "GrandFather Destructor execution!" << endl;
    }
};
class A1 :public GrandFather {
public:
    A1(int age) :GrandFather(age)  { cout << "A1 Constructor execution!" << endl; }
    virtual ~A1() {
        cout << "A1 Destructor execution!" << endl;
    }
};
class A2 :public GrandFather {
public:
    A2(int age) :GrandFather(age)  { cout << "A2 Constructor execution!" << endl; }
    virtual ~A2() {
        cout << "A2 Destructor execution!" << endl;
    }
};
class C:public A1, public A2{
public:
    C(int age):A1(age),A2(age) { cout << "C Constructor execution!" << endl; }
    virtual ~C() {
        cout << "C Destructor execution!" << endl;
    }
};
int main(){
    using namespace multiSucc;
    C ctest(888);
    return 0;
}

Operation results:

         b) Inherit directly from a base class and then indirectly from another base class.

  See the following code:

class GrandFather {
public:
    GrandFather(int age):m_Age(age){ cout << "GrandFather Constructor execution!" << endl; }
    virtual ~GrandFather() {
        cout << "GrandFather Destructor execution!" << endl;
    }
};
class A1 :public GrandFather {
public:
    A1(int age) :GrandFather(age) { cout << "A1 Constructor execution!" << endl; }
    virtual ~A1() {
        cout << "A1 Destructor execution!" << endl;
    }
};
class C:public A1, public GrandFather{
public:
    C(int age):A1(age),GrandFather(age)  { cout << "C Constructor execution!" << endl; }
    virtual ~C() {
        cout << "C Destructor execution!" << endl;
    }
};
int main(){
    using namespace multiSucc;
    C ctest(888);
    return 0;
}

Operation results:

         However, these two special cases will lead to -- "when we read and write the member variables of the same base class, we will cause an ambiguous error! As shown in the figure below:

         Therefore, even if these two special cases of inheriting the same base class can deceive the compiler and let your code pass at the beginning, you are at a loss when you want to do things later. Because here you cause the GrandFather class to be inherited twice, its member variables are constructed twice, and they still belong to different parent class components of A1 and A2. However, after class C is inherited, the two names are the same, resulting in name conflict. This is very bad code! This is the ambiguity of base class member variables in multi inheritance.

        In order to solve the problem of multi inheritance, the important knowledge point of virtual base class and virtual inheritance is introduced!

         virtual base class: no matter how many times this base class will appear in the subsequent inheritance system (how many times it will be inherited), its derived classes will contain only one shared sub content component of the base class.

        In the above example, if A1 and A2 are virtually inherited from GrandFather class, then this GrandFather class becomes a virtual base class. At this time, when generating the object of class C and do ing read-write operations on the member variable virtually inherited from GrandFather class, there is no problem!

        Virtual inheritance: do inheritance with virtual keyword!

         Format: class subclass: virtual inherited type base class | class subclass: inherited type virtual base class

        For example: class A1: virtual public GrandFather indicates that class A1 inherits from the base class GrandFather

                    Class A2: public virtual GrandFather indicates that class A2 inherits from the base class GrandFather

         be careful:

        ① As long as the subclass virtual inherits the base class, the base class will automatically become a "virtual base class"!

         ② It only works for grandchildren!

         ③ The virtual base class must virtual inherit all its subclasses in the past, so as to ensure that the grandchildren of the virtual base class can virtual inherit itself and only produce a component of the virtual base class!

         In the above example code, when both A1 and A2 classes are virtually inherited from the GrandFather class, and because class C inherits from A1 and A2, then the GrandFather class is the virtual base class, then its grandparent class C will only generate a code of the virtual base class component, or the child class will only generate an instance object of the shared virtual base class (that is, the grandchildren inherit the virtual base class)

         See the following code:

class GrandFather {
public:
    int m_Age;
    GrandFather(int age):m_Age(age) {
        cout << "GrandFather Constructor execution!" << endl;
    }
    virtual ~GrandFather() {
        cout << "GrandFather Destructor execution!" << endl;
    }
};
class A1 : public virtual GrandFather {//Indicates that class A1 inherits from the base class GrandFather
public:
    A1(int age):GrandFather(age) {
        cout << "A1 Constructor execution!" << endl;
    }
    virtual ~A1() {
        cout << "A1 Destructor execution!" << endl;
    }
};
class A2 :virtual public GrandFather {
public:
    A2(int age) :GrandFather(age) {
        cout << "A2 Constructor execution!" << endl;
    }
    //A2(int age, string name) :GrandFather(age, name) {
    //    Cout < < A2 constructor execution! "< < endl;
    //}
    virtual ~A2() {
        cout << "A2 Destructor execution!" << endl;
    }
};
class C:public A1, public A2 {
public:
    C(int age):A1(age), A2(age),GrandFather(age) {//In virtual inheritance, Grandpa's composition is initialized directly by the bottom grandson!
        cout << "C Constructor execution!" << endl;
    }
    virtual ~C() {
        cout << "C Destructor execution!" << endl;
    }
};
int main(){
    using namespace multiSucc;
    C ctest(888);
    ctest.m_Age = 99999;
    return 0;
}

Operation results:

         Run successfully! There will be no ambiguous and vague access to the inherited member variable m_Age! That is, deal solves the ambiguity of the base class member variable!

         Of course, in the case of multiple inheritance, you can also directly give subclasses that inherit the same base class and overwrite the inherited variables with member variables with the same name. Indeed, you can achieve the effect of no virtual inheritance, but the premise of this do is to destroy the inheritance writing of the constructor of each subclass (that is, each child class must initialize the components of its direct base class with the constructor of its direct base class!) , let them not initialize the m_Age corresponding to the inherited base class, which will lead to an extra m_Age of the base class you don't use in the objects of each subclass. This is not in line with the idea of Effective c + +, and it is very inefficient to write multiple inheritance!!!

         Three additional points:
         ① Now the grandparent class is used to construct the components of the virtual base class GrandFather. If the grandparent class C inherits from other child classes, the components of the GrandFather class will be initialized by the children of class C. in other words, the components of the virtual base class (GrandFather) are initialized by the lowest derived class!

         ② Initialization order: the compiler must initialize the components of the virtual base class first, and then initialize the components of other classes in the order of the derived list.

         For example, modify the initialization list of class C in the above code to:   class C:public A2, public A1

Then the operation result is:

         ③ If there are multiple virtual inheritance in the inheritance system, that is, when there are multiple virtual base classes, which virtual base class will be initialized first?

        A: at this time, the compiler will trace back and forth according to the derivation (inheritance) list in your lowest class to see whether these direct base classes contain virtual base classes one by one. If so, the components of this class will be constructed first, otherwise they will be constructed in the order of inheritance list! (the components of objects in which virtual base classes are traced first will be constructed first)

         See the following code:

class GrandFather {
public:
    int m_Age;
    GrandFather(int age):m_Age(age) { cout << "GrandFather Constructor execution!" << endl; }
    virtual ~GrandFather() {
        cout << "GrandFather Destructor execution!" << endl;
    }
};
class A1 : public virtual GrandFather {//Indicates that class A1 inherits from the base class GrandFather
public:
    A1(int age):GrandFather(age) { cout << "A1 Constructor execution!" << endl; }
    virtual ~A1() {
        cout << "A1 Destructor execution!" << endl;
    }
};
class A2 :virtual public GrandFather {//Indicates that class A1 inherits from the base class GrandFather
public:
    A2(int age) :GrandFather(age) { cout << "A2 Constructor execution!" << endl; }
    virtual ~A2() {
        cout << "A2 Destructor execution!" << endl;
    }
};
class B{
public:
    int m_b;
    B(int mb):m_b(mb) { cout << "B Constructor execution!" << endl; }
    virtual ~B() {
        cout << "B Destructor execution!" << endl;
    }
};
class BB : public virtual  B{//Indicates that class BB inherits from base class B
public:
    BB(int mb) :B(mb) { cout << "BB Constructor execution!" << endl; }
    virtual ~BB() {
        cout << "BB Destructor execution!" << endl;
    }
};
class C:public A1, public A2,public BB {
//A1, A2 and BB are virtual inheritance of do, so the compiler will call the constructor of the corresponding virtual base class in order according to the inheritance list!
public:
    C(int age):A1(age), A2(age),GrandFather(age),BB(age),B(age) {//In virtual inheritance, the grandson actually initializes grandpa's component directly!
        cout << "C Constructor execution!" << endl;
    }
    virtual ~C() {
        cout << "C Destructor execution!" << endl;
    }
};

Operation results:

         It can be seen from the operation results that it is in line with the supplementary description of point ③ above!

(5) Summary:

        1) If it is not necessary, please do not choose to write your code in the way of multi inheritance! Very important to the suggestion: if you can solve the problem with single inheritance, don't use multiple inheritance!

        2) If it is really necessary to use multi inheritance, be sure to write your code carefully and always remind yourself whether you need to use virtual inheritance to do multi inheritance! (when the ambiguity of member variables arises, virtual inheritance is needed to do multi inheritance!)

        3) Learning to inherit this knowledge point does not mean that you must use it frequently, but to keep you from getting confused when reading other people's code!

Tags: C++ Back-end

Posted on Mon, 22 Nov 2021 12:27:16 -0500 by Neotropic