Reading Notes Objective C ++ Item 40 wise and careful use of multiple inheritance, too tiveitem

Source: Internet
Author: User

Reading Notes Objective C ++ Item 40 wise and careful use of multiple inheritance, too tiveitem
1. Two camps inherited from each other

When we talk about multi-inheritance (MI), the C ++ committee is divided into two basic camps. One camp believes that if a single inheritance is of a good C ++ nature, it will certainly be better if there are more inheritance. The other camp argues that single inheritance is indeed good, but it is too troublesome to use it. In this clause, my main goal is to let you understand the two points of Multi-inheritance.

2. The names inherited from multiple base classes cannot be the same

First, you need to realize that the same name (such as function or typedef) may be inherited from multiple base classes when using MI for design ). This will lead to ambiguous issues, such:

 1 class BorrowableItem {      // something a library lets you borrow 2  3 public:                     4  5 void checkOut();                // check the item out from the library 6  7 ...                                        8  9 };                                       10 11 class ElectronicGadget {    12 13 private:                             14 15 16 bool checkOut() const; // perform self-test, return whether17 ... // test succeeds18 };19 class MP3Player: // note MI here20 21 public BorrowableItem,    // (some libraries loan MP3 players)22 23 public ElectronicGadget   24 25  26 27 { ... };               // class definition is unimportant28 29 MP3Player mp;30 mp.checkOut();          // ambiguous! which checkOut?

 

Note that in this example, the checkout call is ambiguous, even if only one of the two functions is accessible. (Checkout is public in BorrowableItem and private in ElectronicGadget ). This corresponds to how the overload function is solved in the C ++ rule:Before finding whether a function is accessible,C ++First, identify the best matching function called by the function.. After finding the best matching function, the system checks the accessibility of the function. In this case, checkOut is ambiguous when searching for the name, so it cannot solve the problem of function overloading or determine the best matching function. ElectronicGadget: the accessibility of checkOut is not checked at all.

To solve this ambiguity, you must specify the base class function to call:

1 mp.BorrowableItem::checkOut();   // ah, that checkOut

 

Of course, you can also display the call to ElectronicGadget: checkOut, but ambiguous errors will be replaced by the following errors -- "you are trying to call a private member function"

3. Duplicate data members in diamond multi-inheritance 3.1 diamond inheritance

Multi-inheritance only means to inherit from multiple base classes (more than one), but for multi-inheritance, it is not uncommon to find a higher level base class in the inheritance system. This leads to what we often call the fatal "diamond multi-inheritance ":


In an inheritance system, if there are more than one path between the base class and the derived class, you must check whether the data members in the base class are copied on each path. For example, assume that the File class has a data member named fileName. How many copies of IOFile should be available? On the one hand, it inherits a copy from each base class, so it indicates that IOFile should have two fileName data members. On the other hand, an IOFIle has only one file name, so the fileName inherited from the two base classes should not be repeated.

3.2 How C ++ handles diamond inheritance

C ++ has no position in this debate. It is pleased to support two options. The default option is repeated execution. If this is not what you want, you must change the class containing data (that is, File) to a virtual base class. To achieve this goal, you will use virtual inheritance for all classes inherited from it.

 

 

 

The standard C ++ library contains a multi-inheritance system, just like the above inheritance, but the class template class is not in it. These classes are named basic_ios, basic_istream, basic_ostream, and basic_iostream, they replace File, InputFile, OutputFile, and IOFile respectively.

3.3 virtual inheritance consumes resources and cannot be used at will

From the perspective of correct behavior, public inheritance should always be virtual. If you only consider this, the rule will be simple: you must use virtual public inheritance whenever you use public inheritance. However, correctness is not our only concern. To prevent repeated inherited fields, the compiler will play some tricks behind it, the result is that the objects created by using the virtual inheritance class are larger than those created by the class without the virtual inheritance. Accessing data members in a virtual base class is slower than accessing data members in a non-virtual base class. The details vary with the compiler, but the basic idea is clear: Virtual inheritance consumes resources.

Resource consumption is also reflected in other aspects. The rules used to manage the virtual base class initialization list are more complex and less intuitive than those of non-virtual base classes. The responsibility for initializing the virtual base class is borne by the underlying derived class in the inheritance system. This rule means: (1) classes that inherit from the virtual base class must be aware of the existence of the virtual base class, no matter how far the virtual base class is from the derived class. (2) When a derived class is added to the inheritance system, it must be responsible for initializing the virtual base class (whether it is a direct or indirect virtual base class ).

My advice on using a virtual base class (that is, virtual inheritance) is simple. First, do not use the virtual base class unless you need it. By default, non-virtual base classes are used. Second, if you must use a virtual base class, try not to place data in these classes. In this way, you do not have to worry about the odd behavior of these class initialization (and assignment) Rules. It is worth noting that interfaces in Java and. NET (in many ways equivalent to the virtual base class of C ++) cannot contain any data.

4. Another Application Scenario of Multi-Inheritance

Let's take a look at the C ++ Interface Class under IDEA (see Item 31). This class is modeled for persons:

1 class IPerson {2 public:3 virtual ~IPerson();4 virtual std::string name() const = 0;5 virtual std::string birthDate() const = 0;6 };

 

IPerson customers must rely on IPerson pointers and references for programming, because abstract classes cannot be instantiated. To create an object that can be operated by an IPerson object, IPerson customers use the factory function (Item 31) to instantiate the existing class derived from IPerson:

 1 // factory function to create a Person object from a unique database ID; 2 // see Item 18 for why the return type isn’t a raw pointer 3 std::tr1::shared_ptr<IPerson> makePerson(DatabaseID personIdentifier); 4  5 // function to get a database ID from the user 6 DatabaseID askUserForDatabaseID(); 7 DatabaseID id(askUserForDatabaseID()); 8 std::tr1::shared_ptr<IPerson> pp(makePerson(id)); // create an object 9 // supporting the10 // IPerson interface11 12 ...                                                                            // manipulate *pp via13 // IPerson’s member14 // functions

 

But how can I use makePerson to create the object to which the returned Pointer Points? It is clear that some existing classes derived from IPerson must be instantiated by makePerson.

We call this class CPerson. As a current class, CPerson must provide an implementation for pure virtual functions inherited from IPerson. We can implement this function from the beginning, but it is better to use ready-made components to implement most or all of the necessary functions. For example, assume that the PersonInfo class specified by an old database provides CPerson with the most basic things it needs:

 1 class PersonInfo { 2 public: 3 explicit PersonInfo(DatabaseID pid); 4 virtual ~PersonInfo(); 5 virtual const char * theName() const; 6 virtual const char * theBirthDate() const; 7 ... 8 private: 9 10 11 virtual const char * valueDelimOpen() const; // see12 virtual const char * valueDelimClose() const; // below13 ...14 };

 

You can recognize that this is an old class, because the member function returns constchar * instead of a string object. And if the shoes are suitable, why not wear them? The name of the member function of this class also indicates that the results will be very good.

Now you have found that the PersonInfo design is used to print database fields of different formats. the start and end of these field values are separated by special strings. By default, the start and end Separators of the field values are square brackets. Therefore, the value "Ring-tailedLemur" is formatted as the following string:

[Ring-tailed Lemur]

It is recognized that square brackets are not universally accepted by customers of PersonInfo. The valueDelimOpen and valueDelimClose virtual functions allow the derived classes to specify their own start and end separator strings. The implementation of member functions of PersonInfo calls these virtual functions to add proper delimiters for the returned values. Using PersonInfo: theName as an example, the code may be as follows:

 1 const char * PersonInfo::valueDelimOpen() const 2 { 3  4 return "[";                                                                          // default opening delimiter 5  6 }                                                                                         7  8 const char * PersonInfo::valueDelimClose() const             9 10 {                                                                                        11 12  13 14 return "]";                                                                                      // default closing delimiter15 16 }                                                                                                   17 18 const char * PersonInfo::theName() const                                    19 20 {                                                                                                   21 22 // reserve buffer for return value; because this is                        23 24 // static, it’s automatically initialized to all zeros                         25 26 static char value[Max_Formatted_Field_Value_Length];               27 28 // write opening delimiter                                                           29 30 std::strcpy(value, valueDelimOpen());                                         31 32 append to the string in value this object’s name field (being careful 33 34 to avoid buffer overruns!)                                                            35 36 // write closing delimiter                                                             37 38 std::strcat(value, valueDelimClose());                                          39 40 return value;                                                                                41 42 }                  

Someone may question the outdated Design of PersonInfo: theName (the Special Envoy is used for static caching of a fixed size, which sometimes results in out-of-bounds or thread problems, see Item 21 ), but put this problem aside, we will focus on the following: theName calls valueDelimOpen to generate the start separator for the returned string, then generate the name itself, and finally call valueDelimClose.

Because valueDelimOpen and valueDelimClose are virtual functions, the value returned by theName depends not only on PerosnInfo but also on the derived class of PersonInfo.

As the implementer of CPerson, this is good news, because during intensive reading of IPerson documents, you find that name and birthDate must return a value without separators. That is, a person called "Homer" will return "Homer" instead of "[Homer]" when calling the function.

The relationship between CPerson and PersonInfo is that PersonInfo has some functions that make CPerson implementation easier. Therefore, their relationship is "is-implemented-in-terms-of". We know that this relationship can be expressed in two other forms: by combining (Item 38) and private (Item 39 ). Item 39 indicates that a combination is generally better than Private inheritance, but if a virtual function needs to be redefined, private inheritance is required. In this case, CPerson needs to redefine valueDelimOpen and valueDelimClose, so the combination cannot work here. The simplest and most direct solution is to let CPerson private inherit PersonInfo. Although Item 39 explains that if you do more work, CPerson can effectively redefine the virtual function of PersonInfo by combining and inheriting. Here, we use private inheritance.

However, CPerson must also implement IPerson interfaces, which are used for public inheritance. This also leads to a reasonable multi-inheritance application: combining the public interface of an interface with an implemented private inheritance:

 1 class IPerson {                                      // this class specifies the 2  3 public:                                                  // interface to be implemented 4  5 virtual ~IPerson();                               6  7 virtual std::string name() const = 0;      8  9 virtual std::string birthDate() const = 0;       10 11 };                                                         12 13  14 15 class DatabaseID { ... };            // used below; details are16 // unimportant17 18 class PersonInfo {   // this class has functions19 20 21 public: // useful in implementing22 explicit PersonInfo(DatabaseID pid); // the IPerson interface23 virtual ~PersonInfo();24 virtual const char * theName() const;25 virtual const char * theBirthDate() const;26 ...27 private:28 virtual const char * valueDelimOpen() const;29 virtual const char * valueDelimClose() const;30 ...31 };32 33 class CPerson: public IPerson, private PersonInfo { // note use of MI34 public:35 explicit CPerson(DatabaseID pid): PersonInfo(pid) {}36 virtual std::string name() const // implementations37 { return PersonInfo::theName(); } // of the required38 // IPerson member39 virtual std::string birthDate() const // functions40 { return PersonInfo::theBirthDate(); }41 private: // redefinitions of42 const char * valueDelimOpen() const { return ""; } // inherited virtual43 const char * valueDelimClose() const { return ""; } // delimiter44 45 };                                                                   // functions

 

Expressed in UML:

 

This example shows that multi-inheritance is useful and understandable.

 

5. When to use multi-Inheritance

Finally, multi-inheritance is only another tool in the object-oriented toolkit. Compared with single inheritance, the use and understanding are more complex. Therefore, if you encounter a single inheritance design that is similar to the multi-inheritance design, it is basically better to use the single inheritance design. If you can only come up with more inheritance designs, you should think harder-there must be some ways to make the work of single inheritance work normally. At the same time, multi-inheritance is sometimes the cleanest, easiest to maintain, and most reasonable, which can make the work effective. If you encounter this situation, do not be afraid to use it. Make sure that you use private inheritance reasonably and cautiously.

 

6. Summary
  • Multi-inheritance is more complex than single-inheritance. It causes new ambiguity, So virtual inheritance is required.
  • The use of virtual inheritance increases the volume, reduces the speed, and increases the complexity of initialization and assignment. Multi-inheritance is the most practical case when there is no data in the virtual base class.
  • Multi-inheritance also has a reasonable use scenario. One application scenario involves combining the public inheritance of the Interface Class and the private inheritance of the implementation class.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.