C ++ multi-Inheritance
Although most applications use the public inheritance of a single base class, in some cases, single inheritance is not enough because it may not be able to model the problematic domain or cause unnecessary complexity to the model. In this case, multiple inheritance can directly model the application. 1. Multi-inheritance of basic concepts is the ability to inherit from more than one direct base class derived class. Multi-inheritance Derived classes inherit the attributes of their parent class. Class ZooAnimal {}; class Bear: public ZooAnimal {}; class Endangered {}; class Panda: public Bear, public Endangered {}; Note: (1) Same as a single inheritance, classes can be used as the basis class for multi-inheritance only after being defined. (2) There is no language restriction on the number of base classes that a class can inherit. However, in a given derived list, a base class can only appear once. 1. Multi-inherited Derived classes inherit the State Panda ying_yang ("ying_yang") from each base class "); the ying_yang object contains a Bear subclass object, an Endangered subclass object, and non-static data members declared in the Panda class. As shown in: 2. The derived class constructor initializes all base classes. The constructor can pass values to zero or multiple base classes in the early constructor initialization formula. Panda: Panda (string name, bool onExhibit): Bear (name, onExhibit, "Panda"), Endangered (Endangered: critical) {} the constructor can only control the values used to initialize the base class, but cannot control the constructor order of the base class. The base class constructor is called according to the sequence in which the base class constructor appears in the class derivation list. For Panda, the initialization order of the base class is: (1) ZooAnimal. (2) Bear: the first direct base class. (3) Endangered, the second direct base class, which has no base class. (4) Panda, initializes its own member, and then runs the function body of its constructor. Note: The Calling sequence of constructor is not affected by the base classes in the constructor initialization list, nor by the order in which the base classes appear in the constructor initialization list. For example, the constructor Panda: Panda (): Endangered (Endangered: critical) {} implicitly calls Bear's default constructor, even though it does not appear in the constructor initialization list, but it is still called before the Endangered class constructor. 3. The Destructor are called in reverse order according to the constructor running order. Panda, Endangered, Bear, ZooAnimal. 2. When converting to a single base class of multiple base classes, the pointer or reference of the derived class can be automatically converted to the pointer or reference of the base class. For multi-inheritance, the pointer or reference of a derived class can be converted to any pointer or reference of its base class. NOTE: In the case of multiple inheritance, it is more likely to encounter ambiguity conversion. The compiler will not try to differentiate the conversion between base classes based on the conversion of the derived classes. It is equally good to convert to each base class. For example, void print (const Bear &); void print (const Endangered &); when you call print through the Panda object, a compilation error occurs. 1. Like single inheritance, searching based on pointers or reference types can only access members defined (or inherited) in the base class with pointers or references of the base class, you cannot access the Members introduced in the derived class. When a class is derived from multiple base classes, there is no implicit relationship between the base classes and one base class pointer is not allowed to access members of other base classes. Example: class ZooAnimal {public: virtual void print () {} virtual ~ ZooAnimal () {}}; class Bear: public ZooAnimal {public: virtual void print () {cout <"I am Bear" <endl;} virtual void toes () {}}; class Endangered {public: virtual void print () {}virtual void highlight () {cout <"I am Endangered. highlight "<endl;} virtual ~ Endangered () {}}; class Panda: public Bear, public Endangered {public: virtual void print () {cout <"I am Panda" <endl ;} virtual void highlight () {cout <"I am Panda. highlight "<endl;} virtual void toes () {} virtual void cuddle () {} virtual ~ Panda () {cout <"Goodby Panda" <endl ;}}; when the following call occurs: int main () {Bear * pb = new Panda (); pb-> print (); // OK: Panda: print // pb-> cuddle (); // error: not part of Bear interface // pb-> highlight (); // error: not part of Bear interface delete pb; // Panda ::~ Panda Endangered * pe = new Panda (); pe-> print (); // OK: Panda: print // pe-> toes (); // error: not part of Endangered interface // pe-> cuddle (); // error: not part of Endangered interface pe-> highlight (); // OK: Panda :: highlight delete pe; // Panda ::~ Panda return 0;} 2. Determine which virtual destructor to use. If all the underlying Classes define their destructor as virtual functions, use the following methods to delete pointers, the processing of virtual destructor is consistent. Delete pz; // pz is a ZooAnimal * delete pb; // pb is a Bear * delete pp; // pp is a Panda * delete pe; // pe is a Endangered * assume that the above four pointers point to the Panda object, then the same sequence of destructor calls occurs in each case, that is, the order of the constructor is backward: call the Panda destructor through the Virtual mechanism, and then call the destructor of Endangered, Bear, and ZooAnimal in sequence. Iii. Multi-inheritance: control the replication of Multi-inheritance Derived classes use the base class's own copy constructor and value assignment operator. The Destructor implicitly constructs, assigns values, or revokes each base class. Below we will do a few small experiments: 1 class ZooAnimal 2 {3 public: 4 ZooAnimal () 5 {6 cout <"I am ZooAnimal default constructor" <endl; 7} 8 ZooAnimal (const ZooAnimal &) 9 {10 cout <"I am ZooAnimal copy constructor" <endl; 11} 12 virtual ~ ZooAnimal () 13 {14 cout <"I am ZooAnimal destructor" <endl; 15} 16 ZooAnimal & operator = (const ZooAnimal &) 17 {18 cout <"I am ZooAnimal copy operator =" <endl; 19 20 return * this; 21} 22}; 23 class Bear: public ZooAnimal24 {25 public: 26 Bear () 27 {28 cout <"I am Bear default constructor" <endl; 29} 30 Bear (const Bear &) 31 {32 cout <"I am Bear copy constructor" <endl; 33} 34 virtual ~ Bear () 35 {36 cout <"I am Bear destructor" <endl; 37} 38 Bear & operator = (const Bear &) 39 {40 cout <"I am Bear copy operator =" <endl; 41 42 return * this; 43} 44}; 45 class Endangered46 {47 public: 48 Endangered () 49 {50 cout <"I am Endangered default constructor" <endl; 51} 52 Endangered (const Endangered &) 53 {54 cout <"I am Endangered copy constructor" <endl; 55} 56 virtual ~ Endangered () 57 {58 cout <"I am Endangered destructor" <endl; 59} 60 Endangered & operator = (const Endangered &) 61 {62 cout <"I am Endangered copy operator =" <endl; 63 64 return * this; 65} 66}; 67 class Panda: public Bear, public Endangered68 {69 public: 70 Panda () 71 {72 cout <"I am Panda default constructor" <endl; 73} 74 Panda (const Panda &) 75 {76 cout <"I am Panda copy constructor" <endl; 77} 78 virtual ~ Panda () 79 {80 cout <"I am Panda destructor" <endl; 81} 82 Panda & operator = (const Panda &) 83 {84 cout <"I am Panda copy operator =" <endl; 85 86 return * this; 87} 88}; or the previous class, I just removed unnecessary virtual functions. Next I will perform the following operations: int main () {cout <"TEST 1" <endl; Panda ying_ying; cout <endl; cout <"TEST 2" <endl; Panda zing_zing = ying_ying; cout <endl; cout <"TEST 3" <endl; zing_zing = ying_ying; cout <endl; return 0;} has no doubt about this result. First, call the base class constructor and then the derived class. First, call the default constructor to construct a zing_zing object, then call the copy constructor to copy ying_ying to zing_zing. Note: here we use the copy constructor instead of the value assignment operator. When should we use the value assignment operator? Let's look at the result of TEST3: In this case, the value assignment operator is called: assign values after both objects have been allocated memory. The base class also defines operator =. Why not call the base class operator =? Let's comment out operator = of the Panda class and redo TEST3. The interesting result is that the combined value assignment operator of Panda calls operator = Of the two base classes. We come to the following conclusion: If a derived class defines its own copy constructor or value assignment operator, it is responsible for copying (assigning values) All the base class sub-parts, instead of calling the base class corresponding functions. The corresponding functions of the base class are automatically called only when the derived class uses the copy constructor or value assignment operator of the merged version. Finally, let's take a look at the performance of the Destructor: the behavior of the Destructor is as expected. Here I have not shown that zing_zing is the object defined after ying_ying, therefore, the zing_zing constructor executes the constructor first (the first four rows), and the last four rows represent the execution of the ying_ying constructor. If a class with multiple base classes defines its own destructor, The Destructor is only responsible for clearing the derived classes. 4. The scope of classes under multi-inheritance is under multi-inheritance, and multiple base class scopes can enclose the scope of a derived class. When searching, check that all base classes inherit the subtree, for example, querying the Endangered and Bear/ZooAnimal subtree in parallel. If the name is found on multiple child trees, the name must explicitly specify which base class to use. Otherwise, the use of this name is ambiguous. For example, if both the Endangered class and the Bear class have the print function, ying_ying.print () will cause a compilation error. Note: (1) the derivation of the Panda class causes two members named print to be legal. Derivation only leads to potential ambiguity. If no Panda object calls print, this ambiguity can be avoided. You can call Bear: print or Endangered: print. (2) Of course, if a declaration is found on only one base class subtree, no error will occur. The following is a small experiment: class ZooAnimal {public: // void print (int x) {}}; class Bear: public ZooAnimal {public: void print (int x) {}}; class Endangered {public: void print () {}}; class Panda: public Bear, public Endangered {public :}; TEST1: set the two print parameter tables of the base classes Bear and Endangered to different. TEST2: Remove print from Bear and add print to ZooAnimal. TEST3: Set print in Endangered to private access. In the above three cases, when I call ying_ying.print () or ying_ying.print (1) In this way, all show compilation errors (ambiguity ). We come to the conclusion that the name search process is like this. First, the compiler finds a matching statement (finding two matching statements leads to ambiguity ), then the compiler determines whether the declaration found is legal. Therefore, when we call such a function, ying_ying.Bear: print () should be like this ().