A detailed example of C++ multiple inheritance and polymorphism

  • 2020-10-23 21:04:55
  • OfStack

1. Two semantic problems of multiple inheritance

For example:


#include <iostream>
using namespace std;
class BaseA {
public:	void fun() { cout << "A.fun" << endl; }
};
class BaseB {
public:
	void fun() { cout << "B.fun" << endl; }
	void tun() { cout << "B.tun" << endl; }
};
class Derived :public BaseA, public BaseB {
public:
	void tun() { cout << "D.tun" << endl; }
	void hun() { fun(); } // This is where the call occurs 2 nature , Compile will not pass 
};
int main() {
	Derived d, * p = &d; d.hun();
	return 0;
}

The name of the class is limited

[

void hun() { BaseA::fun(); } // Override line 14 of the above code to qualify the called function with the BaseA class name
d.BaseB::fun(); // Class name qualification is used when a derived class object calls a function of the base class name
p- > BaseB::fun(); // Class name qualification is used when a derived class pointer calls a function of the base class name

]

Name rule

If there are two or more containment scopes, the outer layer declares a name and the inner layer does not declare the same name again, then the outer name is visible to the inner layer; If the inner layer declares the same name, the outer name is not visible to the inner layer -- the hide (mask) rule.

In the derivation hierarchy of a class, the member of the base class and the newly added member of the derived class have the class scope, and the scope of the two is different: the base class is in the outer layer, and the derived class is in the inner layer. If a derived class declares a new member with the same name as a base class member, the new member of the derived class will mask the base class member with the same name, and only the new member of the derived class can be accessed by using the member name directly. (If you want to use a member inherited from a base class, you should qualify it with the base class name)


#include <iostream>
using namespace std;
class Base {
public:
	void fun() { cout << "A.fun" << endl; }
	Base(int x = 1, int y = 2) :x(x), y(y) {}
	int x, y;
};

class Derived :public Base{
public:
	void tun() { cout << "D.tun" << endl; }
	void fun() { cout << "D.fun" << endl; }
	Derived(int x = 0) :x(x) {}
	int x;
};
int main() {
	Derived d, * p = &d;
	d.fun();	// The output is 	D.fun
	cout << p->x << " " << p->y << " " << p->Base::x << endl; // The output is  0 2 1
	d.Base::fun(); // The output is 	A.fun
}

2. The virtual base class

Purpose of virtual base classes: Only one member is retained when inheriting from an indirect base class.

The declaration of virtual base class needs to be declared when the derived class definition specifies the inheritance mode. It only needs to add the virtual keyword before the access label (public, protected, private inheritance mode). Note: In order to ensure that a virtual base class inherits only the sequence in a derived class, it should be declared as a virtual base class in all directly derived classes of that base class, otherwise multiple inheritances will still occur.

Derived classes are not only responsible for the initialization of direct base classes, but also for the initialization of virtual base classes. If there is no virtual base class in multiple inheritance, the derived class only needs to initialize the indirect base class, while the initialization of the base class is done by each indirect base class (thus producing multiple copies of the base class stored in each indirect base class).


#include <iostream>
using namespace std;
class Base {
public:
	Base(int n) { nv = n; cout << "Member of Base" << endl; }
	void fun() { cout << "fun of Base" << endl; }
private:
	int nv;
};
class A:virtual public Base {	// The statement Base For the virtual base class , Both are needed as indirect base classes virtual The keyword 
public:
	A(int a) :Base(a) { cout << "Member of A" << endl; }
private:
	int na;
};
class B :virtual public Base { // The statement Base Is the virtual base class, as the indirect base class needs to be used virtual The keyword 
public:
	B(int b) :Base(b) { cout << "Member of B" << endl; }
private:
	int nb;
};
class Derived :public A, public B {
public:
	Derived(int n) :Base(n), A(n), B(n) { cout << "Member of Derived" << endl; }
  // The constructor for the derived class initializes the list, calling the base class first Base Constructor, which in turn calls the indirect base class A , B Constructor of 
  // Because of the virtual base class Base There is no default constructor (no arguments allowed) in, so from Base The constructor initialization table for all derived classes inherited by a class requires the constructor of the base class (including the indirect base class) to be explicitly called to complete the initialization 
private:
	int nd;
};
int main() {
	Derived de(3); de.fun();// Does not produce 2 nature 
	return 0;
}

Description of virtual base classes:

A class can be used as a virtual base class or a non-virtual base class in a family of classes.

If there is no default constructor in the virtual base class (or if the arguments are all default), then the constructor must be explicitly declared in the derived class and the call to the virtual base class constructor listed in the initialization list;

When both virtual base class and non-virtual base class constructors are called in a member initialization list, the virtual base class constructor executes before the non-virtual base class constructor.

3. The virtual functions

Virtual function concept: a member function modified by the virtual keyword is a virtual function, whose function is to achieve polymorphism.

Virtual function instructions:

Virtual functions can only be member functions in a class and cannot be static.

The virtual keyword can only be used in the body of the class, even if the implementation of the virtual function is defined outside the class, it cannot be used with the virtual keyword.

When a member function with the same name as a base class virtual function is defined in a derived class, as long as the number of arguments, type, order and return type of the function are identical with 1 in the base class, the member function of the derived class will automatically become a virtual function whether or not the virtual keyword is used.

Using virtual functions, you can define different implementations of functions using the same function name in both base and derived classes, achieving the goal of "one interface, many ways." When a base class pointer or reference accesses a virtual function, the system determines the virtual function version of the call based on the actual object to which the pointer (or reference) points at run time.

Using virtual functions does not always result in polymorphism, nor does it always result in dynamic linking. For example, by using class name qualification for virtual functions in calls, you can force C++ to use static linking for this function.

In a derived class, polymorphism still occurs when a pointer to a base class member function points to a virtual function and the virtual function is accessed through a base class pointer (or reference) to an object.


#include <iostream>
using namespace std;
class Base {
public:
	virtual void print() { cout << "Base-print" << endl; }
};
class Derived :public Base {
public:
	void print() { cout << "Derived-print" << endl; }
};
void display(Base* p, void(Base::* pf)()) {
	(p->*pf)();
}
int main() {
	Derived d; Base b;
	display(&d, &Base::print);	// The output Derived-print
	display(&b, &Base::print);	// The output Base-print
  return 0;
}

Using virtual functions, the system needs to add 1 fixed space overhead to store the virtual function table, but the time overhead of the system during dynamic linking is very small. Therefore, the polymorphism of virtual function implementation is efficient.

Conditions for virtual functions to be polymorphic (both met)

The inheritance relationship between classes satisfies the assignment compatibility rule.

Overwrite the virtual function with the same name, but the function parameter and return type should remain 1.

Use Pointers (or references) according to assignment compatibility rules;

Accessing virtual functions using base class Pointers (or references); With a pointer (or reference) as a function argument, the function must be a member of the class, can be normal, and can be overloaded.

Virtual destructor

When an object of a derived class is revoked from memory, the derived class's destructor is usually called first, and then the base class's destructor is called later. However, if a derived class object is created with the new operator and a pointer to a base class is defined to this object, then when the object is revoked with the delete operator, only the destructor of the base class is executed, and the destructor of the derived class is not executed, so there is no real undo cleanup of the derived class object.

If you want the delete keyword to act on a base class pointer and also execute a derived class's destructor, you need to declare the base class's destructor as a virtual function.

If the destructor of a base class is declared virtual, the destructor of all derived classes derived from that base class automatically becomes virtual, even if the derived class's destructor has a different name than the base class's destructor.

C++ supports virtual destructors, but does not support fictitious functions, meaning constructors cannot be declared as virtual functions!

Pure virtual function

In many cases, when you cannot give a meaningful definition of a virtual function in a base class, you can describe it as a pure virtual function and leave the definition to the derived class. Pure virtual functions are defined as: virtual return type function name (formal argument list) = 0;

Classes containing pure virtual functions are called abstract classes. An abstract class can only be used as a base class to derive new classes, so it is also called abstract base class. Abstract classes cannot define objects (entities).

4. The polymorphism

Polymorphic meaning: Refers to the same 1 operation on different objects to produce different results.

Overload polymorphism - function overloading, operator overloading Forced polymorphism - also known as type conversion C++ conversion rules between basic data types: char→short→int→ long→unsigned→float→double→long double You can use 3 cast expressions in expressions: static_cast < T > (E) or T(E) or (T)E where E stands for the operational expression (to obtain a value) and T for a type identifier. Forced polymorphism complicates type checking, especially when overloading is allowed, resulting in unresolvable 2-semantics. Type parameterized Polymorphism -- Templates (function templates, class templates) Contains polymorphism - use virtual functions

A class that contains at least one virtual function is called a polymorphic class. Virtual functions enable a program to achieve polymorphic execution results in a dynamic linking manner. This polymorphism is used in the context that a derived class inherits all operations of the base class, or that the operations of the base class can be used to operate on the objects of the derived class. When the operations of the base class cannot adapt to the derived class, the derived class needs to override the operations of the base class. C++ allows you to receive the address of a derived class with a pointer to the base class or bind an object of a derived class with a reference to the base class.

Static and dynamic linking

Linking: The process of combining modules or functions to generate executable code from 1, while allocating memory addresses to each module or function and also allocating the correct memory addresses for external access.

Static linking: Function implementations are bound to function calls at compile time. Static link-up must know all the information needed for the execution of functions or modules at compile time, and its selection of functions is based on the type of pointer (or reference) to the object. In C, all links are static, as is the case with C++ 1.

Dynamic linking: The binding of a function implementation to a function call while the program is running is called dynamic linking (dynamic binding)


#include <iostream>
#define PI 3.14159265
using namespace std;
class Point {
public:
	Point(double x = 0, double y = 0) :x(x), y(y) {}
	double area_static() { return 0; }	// It is not a virtual function and will only bind at compile time, forming a static link 
	virtual double area_dynamic() { return 0; } // With a virtual function declaration, you compile only to check the validity of assignment compatibility, not to bind 
private:
	double x, y;
};
class Circle :public Point {
public:
	Circle(double r = 1.0) :r(r) {} // Because constructors in a base class do not have to pass arguments explicitly, the constructor with default arguments in the base class is automatically called 
	Circle(double x, double y, double r=1.0) :Point(x, y), r(r) {} // overloading 1 A constructor for the parameters of a number of passable coordinate points and radius values 
	double area_static() { return PI * r * r; } // Static binding 
	double area_dynamic() { return PI * r * r; } // Dynamic linking (still virtual), for better readability, can be done at no default virutal The keyword 
private:
	double r;
};
int main() {
	Point o(2.5, 2.5); Circle c(2.5, 2.5, 1);
	Point* po = &o, * pc = &c, & y_c = c;
// The following 5 All are statically linked, regardless of whether the pointer points to a base class or a derived class, because the pointer type is a base class type and the call is not a virtual function 1 Bind as a function in a base class 
	cout << "Point area =" << o.area_static() << endl;	// A value of 0
	cout << "Circle area=" << c.area_static() << endl;	// A value of 3.14159
	cout << "the o area from po:" << po->area_static() << endl; // A value of 0
	cout << "the c area from pc:" << pc->area_static() << endl;	// A value of 0
	cout << "the c area from cite y_c:" << y_c.area_static() << endl; // A value of 0
// The following 3 A pointer (or reference), a virtual function, and the virtual function will pass at run time vptr The pointer finds the virtual table 	 To determine whose function is called based on the actual object the pointer points to, not the pointer type 
	cout << "the o area from po:" << po->area_dynamic() << endl; // A value of 0
	cout << "the c area from pc:" << pc->area_dynamic() << endl; // A value of 3.14159
	cout << "the c area from cite y_c:" << y_c.area_dynamic() << endl; // A value of 3.14159
  // Force the use of static linking 
  cout << "the c area calculated by Point::area_():" << pc->Point::area_dynamic() << endl; // A value of 0
	return 0;
}

Dynamic linking with virtual functions

When calling a virtual function, the virtual function table is first found by the vptr pointer (when compiling a virtual function, the compiler automatically generates an vptr pointer to the virtual function table for the class), and then the real address of the virtual function is found before calling it Derived classes can inherit from the base class's table of virtual functions, and as long as they are member functions with the same name (and with the same arguments), they automatically become virtual functions whether or not they are declared using virtual. If the derived class does not overwrite the virtual function of the inherited base class, the function pointer calls the virtual function of the base class. If the derived class overwrites the virtual function of the base class, the compiler re-addresses the derived class's virtual function, and the function pointer calls the overridden virtual function.

Related articles: