C++ base class and derived class type conversion and virtual base class

Reference:
https://www.jb51.net/article/72586.htm#

.C++ base class and derived class conversion
In public inheritance, private inheritance and protected inheritance, only public inheritance can better retain the characteristics of the base class . It retains all members of the base class except the constructor and destructor. The access rights of the public or protected members of the base class are all retained in the derived class as they are, and the public member functions of the base class can be called outside the derived class to access the private members of the base class. Therefore, the public derived class has all the functions of the base class, and all the functions that the base class can realize can be realized by the public derived class . Non-public derived classes (private or protected derived classes) cannot implement all the functions of the base class (for example, the public member functions of the base class cannot be called outside the derived class to access the private members of the base class). Therefore, only the public derived class is the true subtype of the base class, which fully inherits the functions of the base class.

Different types of data can be converted under certain conditions. For example, integer data can be assigned to a double-precision variable. Before assignment, the integer data is converted to double-precision data, but an integer data cannot be converted. Assign to the pointer variable. This automatic conversion and assignment between different types of data is called assignment compatibility. The question to be discussed now is: Is there an assignment-compatible relationship between the base class and the derived class object, and can the conversion between types be performed?

The answer is yes. There is an assignment compatibility relationship between the base class and the derived class object. Since the derived class contains the members inherited from the base class, the value of the derived class can be assigned to the base class object, and its children can be used when the base class object is used. Class object instead . It is specifically manifested in the following aspects:

The derived class object can be assigned to the base class object
. The derived class object can substitute for the base class object to assign or initialize the reference to the base class object
. If the parameter of the function is the base class object or the reference of the base class object, the corresponding actual parameter can be Use subclass objects
. The address of a derived class object can be assigned to a pointer variable pointing to a base class object, that is, a pointer variable pointing to a base class object can also point to a derived class object

1) Derived class objects can assign values ​​to base class objects.
Subclass (ie, public derived class) objects can be used to assign values to their base class objects. Such as:

  A a1; //定义基类A对象a1
  B b1; //定义类A的公用派生类B的对象b1
  a1=b1; //用派生类B对象b1对基类对象a1赋值

Abandon the members of the derived class when assigning values ​​(because the base class does not define these members at all). That is, "overkill", as shown in the figure:
Insert picture description here
In fact, the so-called assignment is only the assignment of data members, and there is no assignment problem for member functions.

Please note that after assignment, you cannot attempt to access the members of the derived class object b1 through the object a1, because the members of b1 are different from the members of a1. Assuming age is a public data member added in derived class B, analyze the following usage:

a1.age=23;  //错误,a1中不包含派生类中增加的成员
b1.age=21;  //正确,b1中包含派生类中增加的成员

It should be noted that the subtype relationship is one-way and irreversible. B is a subtype of A, and it cannot be said that A is a subtype of B. You can only use subclass objects to assign values ​​to its base class objects, but not to use base class objects to assign values ​​to its subclass objects. The reason is obvious, because base class objects do not contain members of derived classes and cannot assign values ​​to members of derived classes . Similarly, objects of different derived classes of the same base class cannot be assigned values .

2) The derived class object can substitute for the base class object to assign or initialize the reference to the base class object.
If the base class A object a1 has been defined, the reference variable of a1 can be defined:

  A a1; //定义基类A对象a1
  B b1; //定义公用派生类B对象b1
  A& r=a1; //定义基类A对象的引用变量r,并用a1对其初始化

At this time, the reference variable r is an alias of a1, and r and a1 share the same storage unit. You can also initialize the reference variable r with a subclass object, and change the last line above to:

  A& r=b1; //定义基类A对象的引用变量r,并用派生类B对象b1对其初始化

Or keep "A& r=a1;" in line 3 above, and re-assign r:

  r=b1; //用派生类B对象b1对a1的引用变量r赋值

Note that at this time r is not an alias of b1, nor does it share the same storage unit with b1. It is just an alias of the base class part in b1, r and b1 share the same storage unit, r and b1 have the same starting address (still unable to access the new members of the derived class):

class A
{
    
    
public:
	int x = 10;
};
class B:public A
{
    
    
public:
	int y = 5;
};
int main()
{
    
    
	A a1;
	B b1;
	A& r = b1;
	cout << a1.x << " " << b1.x << endl;//输出10 10
	r.x = 3;
	cout << a1.x << " " << b1.x << endl;//输出10 3
	r.y = 7;//报错
}

3) If the parameter of the function is a base class object or a reference to a base class object, the corresponding actual parameter can use a subclass object.
If there is a function:

  fun: void fun(A& r) //形参是类A的对象的引用变量
  {
    
    
    cout<<r.num<<endl;
  } //输出该引用变量的数据成员num

The formal parameter of the function is the reference variable of the object of class A, and the actual parameter should be the object of class A. Since the subclass object is compatible with the assignment of the derived class object, the derived class object can automatically convert the type, and the object b1 of the derived class B can be used as the actual parameter when calling the fun function:

fun(b1);

Output the value of the base class data member num of the object b1 of class B.
Same as before, in the fun function can only output the value of the base class member in the derived class.

4) The address of the derived class object can be assigned to the pointer variable pointing to the base class object, that is, the pointer variable pointing to the base class object can also point to the derived class object.
[Example] Define a base class Student, and then define the public derived class Graduate (Graduate) of the Student class, and output data with a pointer to the base class object. This example mainly shows that a pointer to a base class object is used to point to a derived class object. In order to reduce the length of the program, there are only a few members in each class. The student class only has 3 data members, num (student number), name (name) and score (score), and the graduate class only adds one data member pay (salary). The procedure is as follows:

#include <iostream>
#include <string>
using namespace std;
class Student//声明Student类
{
    
    
public:
  Student(int, string,float); //声明构造函数
  void display( ); //声明输出函数
private:
  int num;
  string name;
  float score;
};
Student::Student(int n, string nam,float s) //定义构造函数
{
    
    
  num=n;
  name=nam;
  score=s;
}
void Student::display( ) //定义输出函数
{
    
    
  cout<<endl<<"num:"<<num<<endl;
  cout<<"name:"<<name<<endl;
  cout<<"score:"<<score<<endl;
}
class Graduate:public Student //声明公用派生类Graduate
{
    
    
public:
 Graduate(int, string ,float,float); //声明构造函数
 void display( ); //声明输出函数
private:
 float pay; //工资
};
//定义构造函数
Graduate::Graduate(int n, string nam,float s,float p):Student(n,nam,s),pay(p){
    
     }
void Graduate::display() //定义输出函数
{
    
    
  Student::display(); //调用Student类的display函数
  cout<<"pay="<<pay<<endl;
}
int main()
{
    
    
  Student stud1(1001,"Li",87.5); //定义Student类对象stud1
  Graduate grad1(2001,"Wang",98.5,563.5); //定义Graduate类对象grad1
  Student *pt=&stud1; //定义指向Student类对象的指针并指向stud1
  pt->display( ); //调用stud1.display函数
  pt=&grad1; //指针指向grad1
  pt->display( ); //调用grad1.display函数
}

The following analysis of the program is very important, please read and think carefully.
Many readers will think that there are two display member functions with the same name in the derived class. According to the rule of the same name coverage, the display function of the graduated object of the derived class should be called. Student:: is called during the execution of the Graduate::display function. The display function outputs num, name, score, and then outputs the value of pay.
In fact, this inference is wrong, let's take a look at the output of the program:

num:1001
name:Li
score:87.5

num:2001
name:wang
score:98.5

The first 3 rows are the data of student stud1, the last 3 rows are the data of graduate student grad1, and the value of pay is not output.

The problem is that pt is a pointer variable that points to the Student class object. Even if it points to grad1, in fact pt points to the part inherited from the base class in grad1.

Through the pointer to the base class object, you can only access the members of the base class in the derived class, but not the members added by the derived class. Therefore, pt->display() calls not the display function added by the graduate object of the derived class, but the display function of the base class, so only the num, name, and score 3 data of graduate student grad1 are output.

If you want to output the pay of graduate student grad1 through a pointer, you can set another pointer variable ptr to the derived class object to make it point to grad1, and then use ptr->display() to call the display function of the derived class object. But this is not very convenient.

From this example, we can see that it is legal and safe to point to the subclass object with the pointer variable pointing to the base class object, and there will be no compilation errors. But in application, it can't fully satisfy people's hopes. People sometimes hope that members of the base class and subclass objects can be called by using the base class pointer. If this can be done, programmers will find it convenient. Subsequent chapters will solve this problem. The way is to use virtual functions and polymorphism (don't redefine inherited non-virtual functions).

.C++ virtual base class detailed explanation of
multiple inheritance is prone to naming conflicts, even if we are careful to name the member variables and member functions in all classes with different names, naming conflicts may still occur, such as the very classic diamond inheritance level. As shown in the figure below:
Insert picture description here
Class A derives class B and class C, and class D inherits from class B and class C. At this time, the member variables and member functions in class A are inherited into class D and become two copies, one from A->B->D this way, another one comes from A->C->D this way.

Keep multiple members of the indirect base class with the same name in a derived class, although different data can be stored in different member variables, but in most cases this is redundant: because keeping multiple member variables not only takes up more Storage space (redundancy) is also prone to naming conflicts (inconsistencies caused by redundancy), and there is rarely such a demand.

In order to solve this problem, C++ provides a virtual base class, so that only a member of the indirect base class is retained in the derived class.

.format:class B1:virtual public B

To declare a virtual base class, you only need to add the virtual keyword in front of the inheritance method. Please see the following example:

#include <iostream>
using namespace std;
class A{
    
    
protected:
  int a;
public:
  A(int a):a(a){
    
    }
};
class B: virtual public A{
    
     //声明虚基类
protected:
  int b;
public:
  B(int a, int b):A(a),b(b){
    
    }
};
class C: virtual public A{
    
     //声明虚基类
protected:
  int c;
public:
  C(int a, int c):A(a),c(c){
    
    }
};
class D: virtual public B, virtual public C{
    
     //声明虚基类
private:
  int d;
public:
  D(int a, int b, int c, int d):A(a),B(a,b),C(a,c),d(d){
    
    }
  void display();
};
void D::display(){
    
    
  cout<<"a="<<a<<endl;
  cout<<"b="<<b<<endl;
  cout<<"c="<<c<<endl;
  cout<<"d="<<d<<endl;
}
int main(){
    
    
  (new D(1, 2, 3, 4)) -> display();
  return 0;
}

operation result:

a=1
b=2
c=3
d=4

In this example, we use the virtual base class. There is only one copy of the member variable a in the derived class D, so you can directly access a in the display() function without adding the class name and domain resolver.

Please note that the constructor of the derived class D is different from the previous usage. In the past, the constructor of the derived class only needed to be responsible for the initialization of its direct base class, and then the direct base class was responsible for the initialization of the indirect base class. Now, since the virtual base class has only one member variable in the derived class, the initialization of this member variable must be directly given by the derived class. If the virtual base class is not initialized directly by the last derived class, but the virtual base class is initialized by the direct derived classes of the virtual base class (such as class B and class C), it may be due to the constructor of class B and class C Different initialization parameters are given to the virtual base class, which results in contradiction. Therefore, it is stipulated that the final derived class is not only responsible for initializing its direct base class, but also for initializing the virtual base class.

Some readers may propose: the constructor of class D adjusts the constructor A of the virtual base class through the initialization table, and the constructors of class B and C also call the constructor A of the virtual base class through the initialization table, so the virtual base Isn't the class constructor called 3 times? Don’t worry, the C++ compilation system only executes the call of the last derived class to the constructor of the virtual base class, and ignores the call to the constructor of the virtual base class by other derived classes of the virtual base class (such as class B and class C). This ensures that the data members of the virtual base class will not be initialized multiple times.

The class structure is shown in the figure:
Insert picture description here

Finally, please note: In order to ensure that the virtual base class is only inherited once in a derived class, it should be declared as a virtual base class in all directly derived classes of the base class, otherwise there will still be multiple inheritance of the base class.

As you can see: Be very careful when using multiple inheritance. Ambiguity issues often arise. The above example is simple. If there are more levels of derivation and multiple inheritance more complicated, programmers will easily fall into the trap, and program writing, debugging, and maintenance will become more difficult. Therefore, many programmers do not advocate the use of multiple inheritance in programs, and only use multiple inheritance when it is relatively simple and not prone to ambiguity or when it is really necessary. Do not use multiple inheritance for problems that can be solved by single inheritance. For this reason, many object-oriented programming languages ​​after C++ (such as Java, Smalltalk, C#, PHP, etc.) do not support multiple inheritance.

The function of virtual base class is summarized:
1. It is mainly used to solve the ambiguity problem caused by multiple inheritance of the same base class that may occur during multiple inheritance.
2. Provide the only base class member for the furthest derived class without repeating multiple copies.

Note:
In the first level of inheritance, the common base class must be designed as a virtual base class.

Guess you like

Origin blog.csdn.net/qq_43530773/article/details/113945504