Multiple inheritance; MI ):
The program may inherit the same name (such as function and typedef) from more than one base class ). This will lead to more opportunities for ambiguity. For example:
Class borrowableitem {
Public:
Void checkout ();
};
Class electronicgadet {
PRIVATE:
Bool checkout () const;
};
Class mp3player: Public borrowableitem
Public electronicgadet
{...};
Mp3player MP;
MP. Checkout (); // ambiguity, which checkout is called?
Even if only one of the two is usable (electronicgadet is private ). This is consistent with the rule that C ++ uses to parse the call of an overloaded function: before seeing whether a function is desirable, C ++ first confirms that this function is the best match for this call. Only by finding the best match can we test the accessibility. The two checkout in this example have the same degree of matching. There is no optimal match. Therefore, the usability of electronicgadget: checkout has never been reviewed by the compiler.
To solve this ambiguity, you must clearly indicate which base class function you want to call:
MP. borrowableitem: checkout ();
You can also call electronicgadget: checkout () explicitly, but then you will get an error of "trying to call the private member function.
More than one base classes is called. These base classes do not often have more advanced base classes in the inheritance system, because they will lead to the "diamond-type multi-inheritance ":
Class file {...};
Class inputfile: Public file {...};
Class outputfile: Public file {...};
Class iofile: Public inputfile,
Public outputfile
{...};
At any time, as long as you have more than one desired route between a base class and a derived class in your inheritance system, you must face the following problem: are you planning to make the members in the base class be copied through each path? Assume that file has a member variable filename, so iofile should have two filename member variables. From another perspective, the simple logic tells us that the iofile object has only one file name, so the filename inherited from two base classes cannot be repeated.
The default practice of C ++ is repeated execution. If that is not what you want, you must make the base class (that is, file) with this data into a virtual base class. All directly inherited classes must adopt "virtual inheritance ":
Class file {...};
Class inputfile: virtual public file {...};
Class outputfile: virtual public file {...};
Class iofile: Public inputfile,
Public outputfile
{...};
The C ++ standard library contains a multi-inheritance system, except that its class is class template: basic_ios, basic_istream, basic_ostream, and basic_iostream.
From the correct behavior, public inheritance should always be virtual. If this is the only idea, the rule is simple: Whenever you use public inheritance, use virtual public inheritance. However, correctness is not the only view. To avoid repeated inherited member variables, the compiler must provide several behind-the-scenes tricks. The consequence is: objects generated by classes inherited by virtual are usually larger than those inherited by non-Virtual. when accessing the member variables of virtual base classes, it is also slower than accessing non-virtual base classes member variables.
The cost of virtual inheritance also includes others: the rules that govern "virtual base classes initialization" are far more complex and intuitive than non-virtual base. The initialization responsibility of virtual base is the responsibility of the most derived class in the inheritance system. 1. If the class is derived from the virtual base class and needs to be initialized, awareness of virtual bases-no matter how far the bases are, 2. When a new derived class joins the inheritance system, it must assume virtual bases (either directly or indirectly).
Our advice on virtual inheritance: First, do not use virtual bases unless necessary. Second, if you must use virtual bases, try to avoid placing data in it. In this way, you don't need to worry about the strange things caused by initialization (and assignment) on these classes.
Let's take a look at this c ++ interface class:
Class iperson {
Public:
Virtual ~ Iperson ();
Virtual STD: string name () const = 0;
Virtual STD: String birthdate () const = 0;
};
// Factory function, which creates a person object based on a unique database ID
STD: tr1: shared_ptr <iperson> makeperson (databaseid personidentifier );
Databaseid askuserfordatabaseid ();
Databaseid ID (askuserfordatabaseid ());
STD: tr1: shared_ptr <iperson> PP (makeperson (ID ));
Assume that a concrete class cperson derived from iperson must provide the implementation code of the pure virtual function inherited from iperson. We can write this, but it is better to use existing components. For example, there is an existing database-related class, personinfo:
Class personinfo {
Public:
Explicit personinfo (databaseid PID );
Virtual ~ Personinfo ();
Virtual const char * thename () const;
Virtual const char * thebirthdate () const;
PRIVATE:
Virtual const char * valuedelimopen () const;
Virtual const char * valuedelimclose () const;
};
Personinfo is designed to help print database fields in various formats. The starting and ending points of each field value are bounded by special strings. The default value is "[", "]".
But not everyone loves square brackets. Therefore, two virtual functions, valuedelimopen and valuedelimclose, are provided to define their own head and tail boundary symbols. Personinfo member functions call these virtual functions and add appropriate boundary symbols to their return values. The code for personinfo: thename looks like this:
Const char * personinfo: valuedelimopen () const
{
Return "["; // default
}
Const char * personinfo: valuedelimclose () const
{
Return "]"; // default
}
Const char * personinfo: thename () const
{
// Reserve the buffer for the returned value: static, automatically initialized to "all 0"
Static char value [max_formatted_field_value_length];
// Write Start symbol
STD: strcpy (value, valuedelimopen ());
Attaches the string in value to the name member variable of this object.
// Write the ending symbol
STD: strcat (value, valuedelimclose ());
Return value;
}
Therefore, the result returned by thename depends not only on personinfo but also on the classes derived from personinfo.
The relationship between cperson and personinfo is that personinfo has several functions that can help cperson easily implement. Therefore, their relationship is-implemented-in-term-. This relationship can be implemented in two ways: Composite and private inheritance. In this example, cperson needs to redefine valuedelimopen and valuedelimclose, so the direct solution is private inheritance.
Cperson also has an interface that must implement iperson, so it must be inherited by public. This leads to a reasonable application of multi-inheritance: combining "Public inherited from an interface" and "Private inherited from an implementation:
Class cperson: Public iperson, private personinfo {
Public:
Explicit cperson (databaseid PID): personinfo (PID ){}
Virtual STD: string name () const
{
Return personinfo: thename ();
}
Virtual STD: String birthdate () const
{
Return personinfo: thebirthdate ();
}
PRIVATE:
Const char * valuedelimopen () const {return "";}
Const char * valuedelimclose () const {return "";}
};
If the only design you can propose involves multiple inheritance, you should think about it again-it can be said that there will be some solutions for a single inheritance. However, sometimes multi-inheritance is indeed the simplest, easiest to maintain, and most reasonable way to complete a task, so don't be afraid to use it. I just decided to use it in a wise and prudent manner.