Mechanics of multiple inheritance compared to temp

2019-06-25 09:25发布

This is a narrower version of the question put on hold due to being too broad.

On pages 6-7 of Modern C++ Design, Andrei Alexandrescu lists three ways in which the multiple inheritance is weaker than templates with respect to building flexible designs. In particular, he states that the mechanics provided by multiple inheritance is poor (the text in square brackets and formatting are mine as per my understanding of the context):

In such a setting [i.e. multiple inheritance], [to build a flexible SmartPtr,] the user would build a multithreaded, reference-counted smart pointer class by inheriting some BaseSmartPtr class and two classes: MultiThreaded and RefCounted. Any experienced class designer knows that such a naïve design does not work.

...

  1. Mechanics. There is no boilerplate code to assemble the inherited components in a controlled manner. The only tool that combines BaseSmartPtr, MultiThreaded, and RefCounted is a language mechanism called multiple inheritance. The language applies simple superposition in combining the base classes and establishes a set of simple rules for accessing their members. This is unacceptable except for the simplest cases. Most of the time, you need to orchestrate the workings of the inherited classes carefully to obtain the desired behavior.

When using multiple inheritance, one can achieve some pretty flexible orchestration by writing member functions that call member functions of several base classes. So, what is the orchestration that is missing from multiple inheritance and present in templates?

Please note that not every disadvantage of multiple inheritance compared to templates goes as an answer here, but only a disadvantage in what Andei calls mechanics in the above quote. In particular, please make sure that you are not talking about one of the other two weaknesses of multiple inheritance listed by Andrei:

  1. Type information. The base classes do not have enough type information to carry on their tasks. For example, imagine you try to implement deep copy for your smart pointer class by deriving from a DeepCopy base class. But what interface would DeepCopy have? It must create objects of a type it doesn’t know yet.

  2. State manipulation. Various behavioral aspects implemented with base classes must manipulate the same state. This means that they must use virtual inheritance to inherit a base class that holds the state. This complicates the design and makes it more rigid because the premise was that user classes inherit library classes, not vice versa.

1条回答
聊天终结者
2楼-- · 2019-06-25 09:41

I think that what Alexandrescu is referring to in the "Mechanics" paragraph is expounded upon in the rest of the chapter. He's referring to how much more flexible policy-based class design is than inheritance-based class design, particularly with respect to the various ways in which policies can be implemented and combined - this in comparison to the single implementation and combination allowed through multiple inheritance.

For instance, when discussing the Creator policy he points out that the policy requires only a Create() method that returns a pointer to the class being created, but doesn't specify that it be virtual or non-static. And he shows several ways in which each policy could be created: a straightforward policy class such as (from section 1.5, skipping the MallocCreator and PrototypeCreator policies)

template<class T>
struct OpNewCreator
{
  static T* Create()
  {
    return new T;
  }
}; 

...

>     //Library code
>     template <class CreationPolicy>
>     class WidgetManager:public CreationPolicy
>     {
>     ...
>     };

...

// Application Code
typedef WidgetManager<OpNewCreator<Widget> > MyWidgetMgr;

or it could be implemented with template template parameters (section 1.5.1) as

//Library Code
template <template <class> class Creation Policy>
class WidgetManager : public CreationPolicy <Widget>
{
...
}

// Application Code
typedef WidgetManager<OpNewCreator> MyWidgetMgr

or (section 1.5.2) - implemented as a template member function:

struct OpNewCreator
{
  template <class T>
  static T* Create()
  {
    return new T;
  }

}

These are examples of the flexible mechanics that are available in a template-based policy class solution and not available in a multiple inheritance solution. These particular examples are not maybe all that exciting, probably because they have to be short and simple for pedagogical reasons.

查看更多
登录 后发表回答