《Effective C++》条款32-40 继承与面向对象设计

条款32:确定你的public继承塑模出is-a关系

总结:

"public继承"意味着 is-a,适用于base class身上的每一件事情也一定适用于derived class身上,

 因为derived 也是一个 base.

 1、常规public继承

class Person {};

class Student:public Person  {};

Public 继承下:每个学生一定是人 Student is a Person ,但是每个人不一定是学生。 

 2、鸟、会飞的鸟、企鹅

出现问题

class Bird {
   public:
    virutal void fly();// 鸟可以飞
};

class Penguin:public Bird{}; // 企鹅是一种鸟

 企鹅是一种鸟,但企鹅不会飞。我们想要的是企鹅包含鸟大部分特性,但是不能包括会飞这个成员函数。

解决办法

1、

// 鸟
class Brid{};
// 会飞的鸟
class FlyingBird:public Bird{
   public:
    virtual void fly();// 成员函数
};
// 企鹅
class Penguin:public Bird{

};

 问题:没必要专门区分会飞的鸟和不会飞的鸟。

 2、为企鹅重新定义fly函数,令他产生一个运行期错误。

专门针对企鹅的fly()函数,定义运行错误

void error (const string& msg);

class Penguin:public Bird{
   // 专门针对企鹅的fly()函数,定义运行错误
   virtual  void fly() {error("Attempt to make a penguin fly"); }
};

问题:只有在运行期才能检测出来。 

3、 不给企鹅定义fly()函数

// 鸟
class Brid{}; // 没有声明fly函数

// 企鹅
class Penguin:public Bird{}; // 没有声明fly函数

penguin p;
p.fly(); // 错误!!!

 在编译期拒绝企鹅飞行fly()。

条款33 避免遮掩继承而来的名称

总结:

1、derived class内同名的名称会遮掩base class内的名称

2、为了让被遮掩的名称重建天日,可使用using 声明式转交函数(forwarding functions)

一、发现问题

class Base{
private:
  int x;
public:
virtual void f1()=0;
virtual void f1(int);
virtual f2();
};

class Derived:public Base{
public:
virtual void f1();
void f4();
};

调用时

Derived d;
int x;

d.f1();//正确,调用Derived::f1
d.f1(x);//错误,Derived::f1遮掩了Base::f1

d.f2();// 正确,调用Base::f2

二、分析

同名函数的话Derived会遮掩Base内的函数,只留下Derived的函数可被使用

Base::f1不再被Derived继承,只剩下Derived::f1。

由于Derived中没有重新定义f2,所以Base::f2仍然存在。

三、解决办法

1、using声明式(在Derived的public区域)

class Base{...};

class Derived:public Base{
public:
using Base::f1;//让Base内名为f1的所有东西在Derived作用域内都可见
virtual void f1();
void f4();
};

意味着:继承base并加上重载函数,同时又希望重新定义或者覆盖一部分,必须为原本会被覆盖的名称引入using声明式。 

导致:Base内所有函数都能被看见

Derived d;
int x;

d.f1();//正确,调用Derived::f1
d.f1(x);//正确,调用Base::f1
d.d4();

2、inline转交函数(forwarding function)

如果只想看见Base 内的某个函数,(Derived 只想继承那个无参版本的f1),using 声明式排不上用场(它会令所有被看见)

class Derived:private Base{// private继承关系,Derived中所有的东西都会变为private
public:
virtual void f1() {Base::f1();} //转交函数,隐式转为inline
};

调用时

Derived d;
int x;
d.f1();//成立,调用的Derived::f1
d.f1(x);//失败,Base::f1被隐藏

条款34 区分接口继承和实现继承

总结:

1、在public继承之下,derived class总是继承base class的接口

2、pure virtual 纯虚函数只具体指定接口函数

3、impure virtual 非纯虚函数具体指定接口继承及缺省实现继承

4、non-virtual 函数具体指定接口继承以及强制性实现继承 

class Shape{
public:
   virtual void draw() const=0; // 纯虚函数 pure virtual
   virtual void error(const string& msg);  // 非纯虚函数 impure virtual
   int objectID() const; // 非虚函数 non-virtual
};

class Retangle: public Shape {};
class Ellipse: public Shape {};

Shape是个抽象class,它的pure virtual 函数draw()使他成为一个抽象class。

一、 pure virtual 纯虚函数只继承接口

class Shape{
public:
   virtual void draw() const=0; // 纯虚函数 pure virtual
   。。。。
}

声明纯虚函数目的:为了让derived class 只继承函数接口,在derived 中实现。

特性:1、被任何继承了他的derived class重新声明。2、在抽象class中没有定义。  

Shape* ps=new Shape; // 错误,Shape是抽象类

Shape* ps1=new Retangle;// 基类指针ps1指向派生类对象
ps1->draw(); // 调用派生类成员函数Retangle::draw()


ps1->Shape::draw();// 调用基类成员函数Shape::draw()

二、impure virtual 非纯虚函数 继承接口和缺省实现

class Shape{
public:
   virtual void error(const string& msg);  // 非纯虚函数 impure virtual
   。。。。
}

派生类中必须支持一个error函数,但如果不想自己写一个,也可以使用Shape class基类提供的缺省版本。 

三、impure virtual 潜在危险

// 基类
class Airplane{
public:
  vitual void fly(const string& destination);// 声明impure virtual函数
};

void Airplane::fly(const string& destination){// 定义impure virtual 函数,缺省函数
   ......   
}

// 派生类AB,声明了fly函数
class ModelA:public Airplane{};
class ModelB:public Airplane{};

// 派生类C,没有声明fly函数
class ModelC:public Airplane{};

基类提供了缺省函数fly(),但是Model A和B都进行了重新定义fly()函数。

Model C没有声明fly函数,如果调用了fly函数,就会试图以Model A或B的方式来飞行。

Airplane* pa=new ModelC;
pa->fly();// 调用缺省实现

 四、impure virtual 解决

问题不在于impure virtual有缺省行为,而是ModelC未说出我要的情况下就继承了该缺省行为。

解决:切断虚函数 接口和 缺省实现 之间的连接。 

条款38 通过复合塑模出has-a 或根据某物实现出

总结:

1、复合和public继承完全不同

2、在应用域,复合意味着has-a(有一个),在实现域,复合意味着is-implemented-in-terms-of(根据某物实现出)。

如果说public继承是一种is-a的关系的话,复合就是has-a关系

1、复合就是在一个类中has a其他类的对象作为自身成员变量,根据另一个对象实现出来。

class Address {};
class PhoneNumber {};

// 主要类
class Person{
   public:
   ....
   private: // private
     string name;
     Address address;// Address类的对象
     PhoneNumber phonenumber;// PhoneNumber类的对象

};

 public 继承是 is-a

 复合是 has-a

Person有一个名称、一个地址、电话。

2、举例实现

set只是利用了list的某些特性,但是不是 is-a 的关系。不能用public继承实现。

template<class T>
class Set{
public: 
bool member(const T& item) const;
void insert(const T& item);

private:
 list<T> rep;// !!! 采用其他类的对象作为自身的成员变量 has a
};

成员函数实现起来,也是可以利用list的功能。

bool Set<T>::member(const T& item) const
{
   return find(rep.begin(),rep.end(),item!=rep.end());
}

bool Set<T>::insert(const T& item)
{
  if(!member(item))   rep.push_back(item);
}

到目前为止,大家应该能理解什么叫is-implemented-in-terms-of了吧,就是依据某物来实现,就像这里的set,就是依据于list来实现自身的结构的,这种情况下也用has-a复合模型。

 

条款39 明智而审慎地使用private继承

总结:

  1. 当你希望访问protected接口的时候,使用private继承比复合更好,因为private继承能够提供访问权限;
  2. 当你希望override它的virtual函数的时候,使用private继承更好,因为继承能够提供override。
  3. 除此之外一般来说,使用复合比使用private继承更好

一、Private继承现象

class Person{...};
class Student:private Person{...};

1、private继承意味着“根据某物实现出”,只有实现部分被继承,接口部分应被忽略。

       D类以私有private形式继承B类,意味着,D对象根据B对象实现而得,再没有其他意涵了。

2、 private继承而来的基类成员都会在派生类中成为private属性,纵使它们在base class中原本是protected或public属性;

3、尽可能使用复合,必要时再采用private继承。用private适用情况:一个想要成为derived class 想访问一个想成为base class的protected成分,或需要重新定义其一或多个virtual函数

二、复合+Public继承的优点

private继承和复合都是“根据某物实现出”,在设计时应尽量使用复合,少使用private继承

class Timer {
public:
	explicit Timer(int freq);
	virtual void onTick()const;//虚函数
};

class Widget{

 private: // 1、类成员变量是private
  class WidgetTimer:public Timer{  //2、public继承
  public:
        virtual void onTick() const;//3、虚函数,重新定义onTick()函数
   };
  WidgetTimer timer;// 4、复合,利用另一个对象
}

WidgetTimer是Widget内部的一个private成员并继承Timer,Widget的Derived class将无法取用WidgetTimer,重新定义其virtual函数。

1、private继承能够重新定义基类中的虚函数(即使不能调用),很多情况下是应该阻止的;而复合很容易控制成员的访问权限。

2、复合能够使编译依赖性降低。

 

条款40 明智而审慎地使用多重继承

总结:

1、virtual虚继承会增加大小、速度、初始化复杂度的成本,如果virtual base class 不带任何数据,将是最具有实用价值的情况。

2、多重继承比单一继承复杂,正确使用情况:其中一个涉及public继承,另一个是private继承某个协助实现的class的结合。

一、出现问题

class A {
public:
	void check() {
		cout << "A" << endl;
	}
 
};
 
class B {
private:
	void check() const {
		cout << "B" << endl;
	}
};
 
class C : public A, public B {
};
 
int main(){
	C c;
	c.check();//无法调用,为解决歧义必须显示指出调用哪个函数例如c.A::check();
}

二、分析

有时候基类和子类之间有一条以上的相通路线,则会导致基类中的数据被复制两份到子类中,解决方法是使用虚继承,避免继承得来的成员变量重复.

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

虚继承问题:,虚继承会增加大小、速度、初始化及赋值等成本。

virtual继承忠告:第一,非必要不要使用virtual bases;

                           第二,如果必须使用virtual bases,尽可能避免在其中放置数据

三、解决办法

多重继承的确有正当用途。其中一个情节涉及“public继承某个interface class”(接口)和“private继承某个协助实现的class”(实现)的两相结合。例子:

class Iperson {
public:
    virtual ~IPerson();
    virtual std::string name() const=0;
    virtual std::string birthDate() const=0;
};
 
class DatabaseID { ... };    //稍后被使用
 
class PersonInfo {           //这个class有若干有用函数,可用以实现IPerson接口
    explicit PersonInfo(DatabaseID pid);
    virtual ~PersonInfo();
    virtual const char* theName() const;
    virtual const char* theBirthDate() const;
    virtual const char* valueDelimOpen() const;
    virtual const char* valueDelimClose() const;
};
 
const char* PersonInfo::valueDelimOpen() const {
    return "[";           //缺省的起始符号
}
const char* PersonInfo::valueDelimClose() const {
    return "]";           //缺省的结尾符号
}
const char* PersonInfo::theName() const { 
    static char value[Max_Formatted_Field_Value_Length];  //保留缓冲区给返回值使用:static,自动初始化为“全0”  
    std::strcpy(value, valueDelimOpen());                 //写入起始符号 
	...                                               //将value内的字符串附到这个对象的name成员变量中 
    std::strcat(value, valueDelimClose());                //写入结尾符号 
    return value; 
}
 
class CPerson : public Iperson, private PersonInfo {     //注意,多重继承
public:
    explicit Cperson(DatabaseID pid) : PersonInfo(pid) {}
    virtual std::string name() const {        //实现必要的IPerson成员函数
	return PersonInfo::theName();
    }
    virtual std::string birthDate() const {     //实现必要的IPerson成员函数
	return PersonInfo::theBirthDate();
    }
private:
    const char* valueDelimOpen() const {       //重新定义继承而来的virtual“界限函数”
		return "";
    }
    const char* valueDelimClose() const {
        return "";
    }
};

Cperson和personInfo的关系是,PersonInfo刚好有若干函数可帮助Cperson比较容易实现出来。因此它们的关系是is-implemented-in-term-of(根据某物实现出)。这种关系可以两种技术实现:复合和private继承。一般复合比较受欢迎,但是在本例之中Cperson要重新定义valueDelimOpen()和valueDelimClose(),所以直接的解法是private继承
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值