Effective C++ 笔记(六)

六:继承与面向对象设计
条款32:确定你的public继承塑模出is-a关系
如果你令class B(派生类)以public形式继承class A(父类),你便是告诉C++编译器说,每一个类型为D的对象同时也是一个类型为B的对象。

请记住:“public继承”意味is-a。适用于base classes身上的每一件事情一定也适用于derived class身上,因为每一个derived class 对象也都是一个base class 对象。

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

class Base{
private:
    int x;
public:
    virtual void mf1() = 0;
    virtual void mf1(int);
    virtual void mf2();
    void mf3();
    void mf3(double);
};
class Derived: public Base{
public:
    virtual void mf1();
    void mf3();
    void mf4();
};

int main(){
    Derived d;
    int x;
    ...
    d.mf1();  //没问题,调用Derived::mf1
    d.mf1(x); //错误!因为Derived::mf1遮掩了Base::mf1
    d.mf2();//没问题,调用Base::mf2
    d.mf3(); //没问题,调用Derived::mf3
    d.mf3(x);  //错误!因为Derived::mf3遮掩了Base::mf3
    return 0;
}

Derived class 可以更改为:

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

如果Derived以private继承Base,而Derived唯一想继承的mf1是那个无参数版本。using声明在这里派不上用场,因为using声明式会令继承而来的某给定名称的所有同名函数在derived class中都可见。此时需要不同的技术,即一个简单的转交函数

class Derived: private Base{
public:
	virtual void mf1()  //转交函数
	{ Base::mf1();}
};
Derived d;
int x;
d.mf1();//很好,调用的是Derived::mf1
d.mf1(x); //错误!Base::mf1()被遮掩了

请记住:1.derived classes内的名称会遮掩base classes内的名称。在public继承下从来没有人希望如此。(is-a)
2.为了让被遮掩的名称再见天日,可使用using声明式或转交函数。

条款34:区分接口继承和实现继承
声明一个pure virtual(纯虚)函数的目的是为了让derived classes只继承函数接口。(对派生类说:你必须提供一个这个函数,但我不干涉你怎么实现它)。
声明(非纯)impure virtual函数的目的,是让derived classes继承该函数接口和缺省实现。(对派生类说:你必须支持一个这个函数,但如果你不想自己写一个,可以使用我提供给你的缺省版本)。
声明non-virtual 函数的目的是为了令derived classes继承函数的接口及一份强制性实现。(对派生类说:这个函数的实现由我定义,你不应该尝试改变其行为)。

请记住:1.接口继承和实现继承不同。在public继承之下,derived classes总是继承base class的接口。
2.pure virtual函数只具体指定接口继承。
3.impure virtual函数具体指定接口继承及缺省实现继承。
4.non-virtual函数具体指定接口继承以及强制性实现继承。

条款35:考虑virtual 函数以外的其他选择
藉由Non-Virtual Interface手法实现Template Method模式。
NVI手法的一个优点隐身在下述代码注释“做一些事前工作”,“做一些事后工作”之中。这意味外覆器确保得以在一个virtual函数被调用之前设定好适当场景,并在调用结束之后清理场景。。如果你让客户直接调用virtual 函数,就没有任何好办法可以做这些事。

class A{
public:
    int healthValue() const{
        ...            //做一些事前工作
        int retVal = doHealthValue();
        ...             //做一些事后工作
        return retVal;
    }
private:
    virtual int doHealthValue() const {
        ...
    }
};

请记住:1.virtual 函数的替代方案包括NVI手法及Strategy设计模式的多种形式。NVI手法自身是一个特殊形式的Template Method设计模式。
2.将机能从成员函数移到class外部函数,带来的一个缺点是,非成员函数无法访问class的non-public成员。
3.tr1::function对象的行为就像一般函数指针。这样的对象可接纳“与给定之目标签名式兼容”的所有可调用物。

条款36:绝不重新定义继承而来的non-virtual函数

class B{
public:
    void mf();
};
class D:public B {
    void mf();//遮掩了B::mf
};
D x;
//如以下行为
B *pB = &x;
pB->mf();
//异于以下行为
D *pD = &x;
pD->mf();
/*	
	造成此行为的原因是,non-virtual 函数如B::mf 和D::mf都是静态绑定
*/

请记住:绝对不要重新定义继承而来的non-virtual函数。

条款37:绝不重新定义继承而来的缺省参数值

class Shape{
public:
    enum ShapeColor
    {
        Red,
        Green,
        Blud
    };
    virtual void draw(ShapeColor color = Red) const = 0;
};
class Rectangle : public Shape {
public:
    //注意,赋予不用的缺省参数值。这真糟糕!
    virtual void draw(ShapeColor color = Green) const;
};
class Circle : public Shape {
public:
    /*
        请注意:以上这么写则当客户以对象调用此函数,一定要指定参数值。因为静态绑定下这个函数并不从其base继承缺省参数值。
        但若以指针(或引用)调用此函数,可以不指定参数值,因为动态绑定下这个函数会从其base继承缺省参数值。
    */
    virtual void draw(ShapeColor color) const;
};

Shape *pr = new Rectangle; //静态类型为Shape *
pr->draw();  //调用Rectangle::draw(Shape::Red)!  Rectangle::draw的缺省参数值应为GREEN,但由于pr的静态类型是Shape *
//,所以此一调用的缺省参数值来自Shape class而非Rectangle。

正确版本:

class Shape{
public:
    enum ShapeColor
    {
        Red,
        Green,
        Blud
    };
    void draw(ShapeColor color = Red) const{
        doDraw(color);
    }
private:
    virtual void doDraw(ShapeColor color) const = 0;
};
class Rectangle : public Shape {
public:
    ...
private:
    virtual void draw(ShapeColor color) const;
};
//由于non-virtual函数应该绝对不被derived classes覆写,这个设计很清楚的使得draw函数的color缺省参数值总是为Red。

请记住:绝对不要重新定义一个继承而来的缺省值,因为缺省参数值都是静态绑定(用的是静态类型的缺省值,不是你自己定义的),而virtual函数----你唯一应该覆写的东西----却是动态绑定。

条款38:通过复合塑模出has-a或“根据某物实现出”
复合是类型之间的一种关系,当某种类型的对象内含它种类型的对象,便是这种关系。
在应用域复合意味has-a(有一个),在实现域,意味is-implemented-in-terms-of(根据某物实现出)。书p186

请记住:1.复合的意义和public继承完全不同。
2.在应用域复合意味has-a(有一个),在实现域,意味is-implemented-in-terms-of(根据某物实现出)。

条款39:明智而审视地使用private继承
一个空类 class Empty{}; 其大小不为0,如不考虑齐位要求,在大多是编译器中sizeof(Empty)=1(安插一个char到空对象中)。如果不独立,如class B:public Empty{private: int x;},则sizeof(B)=sizeof(int) (空白基类最优化)。
现实中的“empty class”并不真的是empty。虽然他们从未拥有non-static成员变量,却往往内含typedefs,enums,static成员变量,或non-virtual函数。

请记住:1.private继承意味is-implemented-in-terms-of(根据某物实现出)。他通常比复合的级别低。但是当derived class需要访问protected base class 的成员,或需要重新定义继承而来的virtual函数时,这么设计是合理的。
2.和复合不同,private继承可以造成empty base最优化。这对致力于“对象尺寸最小化”的程序库开发者而言,可能很重要。

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

请记住:1.多重继承比单一继承更复杂。他可能导致新的歧义性,以及对virtual继承的需要。
2.virtual继承会增加大小、速度、初始化(及赋值)复杂度等等成本。如果virtual base classes不带任何数据,将是最具实用价值的情况。
3.多重继承的确有正当用途。其中一个情节涉及“public继承某个interface class”和“private继承某个协助实现的class”的两相组合。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

二零二三.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值