细读《Effective C++》之十

Chapter 6. Inheritance and Object-Oriented Design

这一章围绕inhertance和OOD进行讨论。

Item 32:如果不是"is-a"关系,就一定不要用public inheritance,因为public inheritance就意味着base class的所有操作对于derived class完全适用。

Item 33:尽量不让base class和derived class拥有相同的变量名和函数名,你也不希望通过类似的声明去区分它们:

using base::func;

Item 34:pure virtual function一般只提供接口继承,虽然它也被可以使用。impure virtual function则用于提供接口和缺省实现。non-virtual function则提供接口和强制实现。

Item 35:本款主要介绍了用non-virtual interface、function pointer、tr1::function等实现的Template Method和Strategy模式,以替代virtual functions。

Item 36:不重新定义public inheritance的non-virtual function,一是为了保证"is-a"的纯洁关系,二是使invariant凌驾于specialization。

Item 37:为了避免dynamic binding的functions和static binding的default parameters在运行中的混淆,就不要重新定义这些default parameters。

Item 38:composition最简单、最直接的一种表现就是"has-a"关系了,而借助composition实现的"is-implementated-in-terms-of"则显得更为实用。

Item 39:由于private inheritance导致base class的接口和实现对derived class可见,却不能为other type class object所用,因此,composition + inheritance是更好地实现方式,EBO除外。

Item 40:MI/VI是很少被用到的,STL中的basic_iostream是一个典型了,如果有现成的classes可直接用于实现,MI用也无妨,只要你知道你在做什么。

Item 38 - 40

条款38:Model "has-a" or "is-implemented-in-terms-of" through composition

还记得"is-a"用public inheritence实现吧?composition有两种情况:"has-a"(application domain)和"is-implemented-in-terms-of"(implementation)。

"has-a"是很好区分的,而composition的另一实现,"is-implemented-in-terms-of"和"is-a"的区别则不明显。正如Item 32中提到的,Square与Rectangle并非"is-a"关系,Set与List同样并非"is-a"关系,而是"is-implemented-in-terms-of"关系:

template<class T>                   // the right way to use list for Set
class  Set {
public
:
  
bool member(const T& item) const
;
  
void insert(const T&
 item);
  
void remove(const T&
 item);
  std::size_t size() 
const
;

private
:
  std::list
<T> rep;                 // representation for Set data

};

Things to Remember
1) Composition has meanings completely different from that of public inheritance.

2) In the application domain, composition means has-a. In the implementation domain, it means is-implemented-in-terms-of.

条款39:Use private inheritance judiciously

private inheritance并不表达"is-a"关系,而是"is-implemented-in-terms-of"关系,说白了,就是可以用base class的implenmentations实现derived class的implenmentations。当然,如果可能,composition是最好的选择。原因如下:

1) 解耦:为了实现"is-implemented-in-terms-of",以允许重写virtual functions为代价是private inheritance的硬伤。而根据需求composite一个public inheritance的derived class object则使得重写的private virtual functions对client透明。

2) 降低编译依存度:composition可以采用仅声明derived class的方式达到避免包含base class的head file的目的。

3) 例外:

// composition
class Empty {};                      // has no data, so objects should use no memory
class HoldsAnInt {                   // should need only space for an int
private :
  
int
 x;
  Empty e;                           
// should require no memory

};

Empty对象默认占用1 byte,如果需要对齐,还不止于此。而:

// private inheritance
class HoldsAnInt: private  Empty {
private
:
  
int x;                             // sizeof(HoldsAnInt) == sizeof(int)

};

这就是所谓的EBO(empty base optimization),EBO一般只在单一继承下才可行。

Things to Remember

1) Private inheritance means is-implemented-in-terms of. It's usually inferior to composition, but it makes sense when a derived class needs access to protected base class members or needs to redefine inherited virtual functions.

2) Unlike composition, private inheritance can enable the empty base optimization. This can be important for library developers who strive to minimize object sizes.

条款40:Use multiple inheritance judiciously

MI的一个可能的问题是abmiguity:

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:                   // note MI here

  public BorrowableItem,           // (some libraries loan MP3 players)
  public  ElectronicGadget
{ ... };                           
// class definition is unimportant


MP3Player mp;
mp.checkOut();                     
// ambiguous! which checkOut?

由于两base classes的匹配程度相同,需明确指明是要调用哪一个:

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

再看:

class  File {
...
protected
:
  
string
 szFileName;
};

class InputFile: public
 File { ... };

class OutputFile: public
 File { ... };

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

IOFile对象会有几个szFileName呢?解决办法就是令File成为virtual base class:

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

virtual inheritance的代价是:

1) 体积增大;

2) 访问成员变量速度变慢;

3) virtual base classes的初始化由most derived class负责。

因此,应尽量避免使用virtual inheritance,如果使用则应尽量避免在virtual base class中放置数据。

class  IPerson {
public
:
  
virtual ~
IPerson();
  
virtual std::string name() const = 0
;
  
virtual std::string birthDate() const = 0
;
};

由于IPerson是abstract class无法被实例化,因此使用IPerson时,必须以pointers或references使用。这让我想到在Item 31中就曾用到的factory function:

//  factory function to create a Person object from a unique database ID;
// see Item 18 for why the return type isn't a raw pointer

std::tr1::shared_ptr<IPerson>  makePerson(DatabaseID personIdentifier);

// function to get a database ID from the user

DatabaseID askUserForDatabaseID();

DatabaseID id(askUserForDatabaseID());
// create an object supporting the IPerson interface

std::tr1::shared_ptr<IPerson>  pp(makePerson(id));
...          
// manipulate *pp via IPerson's member functions

此时,当然还要有个继承自IPerson的derived class CPerson,并实现Iperson中的pure virtual functions,如果有现成的一个类PersonInfo可以实现相关功能,这不正意味着CPerson"is-implemented-in-terms-of"PersonInfo吗?

class CPerson: public IPerson, private PersonInfo {     // note use of MI
public :
  
explicit
 CPerson(    DatabaseID pid): PersonInfo(pid) {}
  
virtual std::string name() const                      // implementations of the required IPerson member

  virtual std::string birthDate() const                 // functions
  { return  PersonInfo::theBirthDate(); }
private:                                                // redefinitions of

  const char * valueDelimOpen() const { return ""; }    // inherited virtual
  const char * valueDelimClose() const { return ""; }   // delimiter
};                                                     // functions

Things to Remember

1) Multiple inheritance is more complex than single inheritance. It can lead to new ambiguity issues and to the need for virtual inheritance.

2) Virtual inheritance imposes costs in size, speed, and complexity of initialization and assignment. It's most practical when virtual base classes have no data.

3) Multiple inheritance does have legitimate uses. One scenario involves 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、付费专栏及课程。

余额充值