Effective C++: Item 40 -- Use multiple inheritance judiciously

注: 此为英文资料整理,如需翻译请私信或留评论

Multiple Inheritance (MI) Definition

Multiple inheritance just means inheriting from more than one base class, but it is not uncommon for MI to be found in hierarchies that have higher-level base classes, too. That can lead to what is sometimes known as the “deadly MI diamond”

IOFile
InputFile
OutputFile
File
class File { ... };
class InputFile: public File { ... }; 
class OutputFile: public File { ... }; 
class IOFile: public InputFile, public OutputFile
{...};

Discussion

When it comes to multiple inheritance (MI), the C++ community largely breaks into two basic camps.

  • If single inheritance (SI) is good, multiple inheritance must be better.
  • Single inheritance is good, but multiple inheritance isn’t worth the trouble

Problem of MI

One of the first things to recognize is that when MI enters the design- scape, it becomes possible to inherit the same name (e.g., function, typedef, etc.) from more than one base class. That leads to new opportunities for ambiguity.
For example:

class BorrowableItem { // something a library lets you borrow 
public:
	void checkOut(); // check the item out from the library
	... 
};
class ElectronicGadget { 
private:
	bool checkOut() const; // perform self-test, return whether
	...		 // test succeeds
};
class MP3Player:
	public BorrowableItem, // (some libraries loan MP3 players)
	public ElectronicGadget
	{ ... }; // class definition is unimportant
	MP3Player mp; 
	mp.checkOut(); // ambiguous! which checkOut?

C++ first identifies the function that’s the best match for the call. It checks accessibility only after finding the best-match function.
To resolve the ambiguity, you must specify which base class’s function to call:

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

Virtual Inheritance

Problem

Any time you have an inheritance hierarchy with more than one path between a base class and a derived class (such as between File and IOFile above, which has paths through both InputFile and OutputFile), you must confront the question of whether you want the data members in the base class to be replicated for each of the paths.

Recall the example:

class File { ... };
class InputFile: public File { ... }; 
class OutputFile: public File { ... }; 
class IOFile: public InputFile, public OutputFile
{...};

If the deadly MI Diamond happened, there would be two possibilities that IOFile class contains File’s data member.

  1. IOFile contains copy of File’s data member from each path, in this case is 2
  2. IOFile contains contains only 1 copy of File’s data member ( This is usually the case we want )

By default, C++ performs the replication.

Solution

If that’s not what you want, you must make the class with the data (i.e., File) a virual base class. To do that, you have all classes that immediately inherit from it using virtual inheritance.

class File { ... };
class InputFile: virtual public File { ... }; 
class OutputFile: virtual public File { ... }; 
class IOFile: 
		public InputFile,
		public OutputFile
{...};
virtual
virtual
IOFile
InputFile
OutputFile
File

From the viewpoint of correct behavior, public inheritance should always be virtual.

Problem

Objects created from classes using virtual inheritance are generally larger than they would be without virtual inheritance. Access to data members in virtual base classes is also slower than to those in non-virtual base classes.

The responsibility for initializing a virtual base is borne by the most derived class in the hierarchy. Implications of this rule include

  1. classes derived from virtual bases that require initialization must be aware of their virtual bases, no matter how far distant the bases are
  2. when a new derived class is added to the hierarchy, it must assume initialization responsibilities for its virtual bases (both direct and indirect)
Solution

First, don’t use virtual bases unless you need to. By default, use non-virtual inheritance. Second, if you must use virtual base classes, try to avoid putting data in them.

One Reasonable Application of MI

Combine public inheritance of an interface with private inheritance of an implementation.

class IPerson { // this class specifies the interface to be implemented
public:
	virtual ~IPerson();
	virtual std::string name() const = 0;
	virtual std::string birthDate() const = 0; 
};

class DatabaseID { ... }; // used below; details are unimportant

class PersonInfo {   // this class has functions
public:				 //useful in implementing the IPerson interface
	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; 
	...
};

class CPerson: public IPerson, private PersonInfo { // note use of MI
public:
	explicit CPerson(DatabaseID pid): PersonInfo(pid) {}
	
	virtual std::string name() const		// implementation of required
	{ return PersonInfo::theName(); }		// IPerson member functions
	virtual std::string birthDate() const 
	{ return PersonInfo::theBirthDate(); }
private:
	const char * valueDelimOpen() const { return ""; } // redefinition of 
	const char * valueDelimClose() const { return ""; }// inhereited virtual
													   // delimiter function

This example demonstrates that MI can be both useful and comprehensible.

Conclusion

If you’ve got an SI design that’s more or less equivalent to an MI design, the SI design is almost certainly preferable. If the only design you can come up with involves MI, you should think a little harder — there’s almost certainly some way to make SI work.

Things to Remember

  • Multiple inheritance is more complex than single inheritance. It can lead to new ambiguity issues and to the need for virtual inheritance.
  • Virtual inheritance imposes costs in size, speed, and complexity of initialization and assignment. It’s most practical when virtual base classes have no data.
  • Multiple inheritance does have legitimate uses. One scenario in- volves combining public inheritance from an Interface class with private inheritance from a class that helps with implementation.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值