Effective C++ 学习笔记(五)

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

(1)public继承意味is-a,适用于base class身上的每一件事情一定也适用于derived classes身上,因为每一个devided class对象也是一个base class对象,但是每一个适用于devided class对象身上的每一件事情并不一定适用于base class。

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

(1)derived class内的名称会遮掩base classes内的名称,就像局部对象会遮掩全局对象一样

#include<iostream>
using namespace std;

class Base
{
private:
    int x;
public:
    virtual void mf1() = 0;
    virtual void mf1(int a)
    {
        cout << "Base::mf1(int a):"<< a << endl;
    }
    virtual void mf2()
    {
        cout <<"Base::mf2:" << endl;
    }
    void mf3()
    {
        cout << "Base::mf3:" << endl;
    }
    void mf3(double a)
    {
        cout << "Base::mf3(double)" << a << endl;
    }
};

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


void main()
{
    Derived d;
    int x = 10 ;

    d.mf1();
    d.mf1(x);
    d.mf2();
    d.mf3();
    d.mf3(x);
}

如果在derived中不加using Base::mf1和using Base::mf3这两句话的话,derived中的mf1函数就会把Base类中的mf1函数遮掩掉,在调用d.mf1(x)和d.mf3(x)时就会编译报错,说找不到这样的函数,这是因为derived中的mf1等函数将base类中这样的函数遮掩掉了。

条款34:区别接口继承和实现继承

(1)身为class的设计者,有时候你希望derived class只继承成员函数的接口(也就是声明);有时候你希望derived classes同时继承函数的接口和实现,但有希望能够覆写(override)他们所继承的实现;有时你希望derived class同时继承函数的接口和实现,并且不允许覆写任何东西。为了实现这些功能可以用pure virtual实现只继承接口,impure virtual实现继承接口和实现并能覆写,一般的函数能够实现继承接口和实现但不能覆写。

(2)因为impure virtual实现继承接口和实现并能覆写,如果子类并不想使用默认继承来的impure virtual的话,该怎么解决呢?

#include<iostream>
using namespace std;

class Base
{
public:
virtual void print() =0; //迫使子类必须实现该接口
};
void Base::print()//为子类提供一个可以调用的函数,但是必须是自己调用
{
    cout <<"Base::print()" << endl;
}

class Derived:public Base
{
public:
    
 void print()
 {
     Base::print();
     //cout << "Derived::print()" << endl;
 }
};

void main()
{
   Base *pBase = new Derived;
   pBase->print();
}
可以使用这种解决办法,为virtual函数提供一个默认的实现,因为子类必须要实现virtual函数,所以子类就不会默认的继承父类已经实现过的方法,子类可以调用父类提供的默认实现方法,但是调用该方法必须的是子类显式的调用,这样就能避免子类默认情况下继承父类的impure virtual函数的问题。

条款25:考虑virtual函数以外的其他选择

(1)藉由Non-Virtual Interface手法实现Template Method模式

#include<iostream>
using namespace std;

class GameCharacter
{
public:
    int healthValue() const
    {
        ..... //做一些事前工作
        int retVal = doHealthValue();
        ..... //做一些事后工作
    }
private:
    virtual int doHealthValue() const
    {
        .....
    }
};
在这个类中,在healthValue函数中,调用了doHealthValue()这个虚函数,在调用这个虚函数之前做了一些初始化工作,在调用这个函数之后又做了一些初始化工作,这些都可以看做是骨架,doHealthValue()这个虚函数可以延后在子类中进行扩充,这就是设计模式中的template模式。

(2)藉由Funtion Pointers实现Strategy模式

这个方法的思想是,在这个类中保存另一个类的指针或者对象,通过保存的指针或对象调用该对象的方法,实现Strategy模式。可以将类中的virtual函数都替换为指针成员变量,这样比virtual更加灵活。不需要构造更多的类。

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

(1)如果重新定义了继承而来的non-virtual函数,就会出现遮掩现象(子类的方法这样父类的方法),并且non-virtual函数体现的是不变性,如果要在子类中重新定义这个函数,就说明这个函数是要在子类中改变的就不应该是non-virtual函数而应该是virtual函数。

#include<iostream>
using namespace std;
class B
{
public:
    void mf()
    {
        cout << "B::mf" << endl;
    }
};
class D:public B
{
public:
    void mf()
    {
        cout << "D::mf" << endl;
    }
};
void main()
{
    D x;
    B *pB = &x;
    pB->mf();
    D *pD = &x;
    pD->mf();
}
这将因为指针的类型决定调用的是谁的函数的问题,而virtual函数不存在这样的问题。绝对不要重新定义继承而来的non-virtual函数。

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

(1)因为virtual函数是动态绑定的,而缺省参数却是静态绑定的,所以函数调用的是子类的virtual函数,但是参数却是父类的缺省参数,即使子类中也定义了参数的缺省参数,使用的仍是父类的缺省参数。

#include<iostream>
using namespace std;
class Shape
{
public:
    enum ShapeColor{ Red,Green,Blue};
    virtual void draw( ShapeColor color = Red ) const = 0;
};

void Shape::draw(ShapeColor color) const 
{
    cout << color << endl;
}
class Rectangle: public Shape
{
public:
    virtual void draw( ShapeColor color = Green ) const
    {
        cout << color << endl;
    }
};
class Circle :public Shape
{
public:
    virtual void draw( ShapeColor color) const
    {
        cout << color << endl;
    }
};
void main()
{
    Shape *ps1;
    ps1 = new Rectangle;
    ps1->draw();
    delete ps1;
    Shape *ps2;
    ps2 = new Circle;
    ps2->draw();
    delete ps2;
}

(2)在遇到virtual函数的缺省参数时,可以使用下面的方法来替代设计。

#include<iostream>
using namespace std;
class Shape
{
public:
    enum ShapeColor{ Red,Green,Blue};
    void draw( ShapeColor color = Red ) const
    {
        doDraw( color );
    }
private:
    virtual void doDraw( ShapeColor color ) const = 0;
};

void Shape::doDraw(ShapeColor color) const 
{
    cout << color << endl;
}
class Rectangle: public Shape
{
public:
    virtual void doDraw( ShapeColor color ) const
    {
        cout << color << endl;
    }
};

因为non-virtual函数绝对不被derived class 覆写,这个设计很清楚的使用color的缺省参数值。

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

(1)复合的意义和public继承完全不同。public继承是is-a,复合的意义是has-a,is-a体现的是子类是父类,has-a体现的是一个类中有另外一个类,但两个类不能相互替代,如果类A包含了类B,则类A可以调用类B的函数。

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

(1)如果是类Dpublic继承类B,体现的是is-a,所以类D可以自动将类D转化成类B,如果是private继承,那么体现的将不是is-a,private体现的是类D和类B是没有必然的联系,只是类D想使用类B的一些方法或者函数,类D和类B之间没有必然的联系,所以呢类D将不会自动转化为类B。如下面程序:

#include<iostream>
using namespace std;
class B
{
public:
    int x;
    virtual void print()
    {
        cout << "B::print" << endl;
    }
};
class D: private B
{
public:
    virtual void print()
    {
        cout << "D::print" << endl;
    }
};

void callPrint( B &rb )
{
    rb.print();
}
void main()
{
   B b;
   D d;
   callPrint( b );
   callPrint( d );//将会报错:error C2243: 'type cast' : conversion from 'class D *' to 'class B &' exists, but
                   //is inaccessible
}
(2)private继承和复合的作用都是使用某些类的方法,那么两者怎么取舍呢?首先,看下面的例子:

#include<iostream>
using namespace std;
class Empty
{
};

class HoldAnInt
{
private:
    int x;
    Empty e;//应该不需要任何内存的
};
会发现sizeof(HoldAnInt)>sizeof(int),因为大小为0的独立对象,c++官方勒令安插一个char(或其它如int)到空对象内,所以呢这样的话用private会比较节省空间,对于要节省空间很重要。

如果要是用private继承的话,如果子类就可以改写父类的一些方法,可能我们的本意只是想让子类使用这些方法,这将违背我们的意愿,所以这时候用组合还是比较好。

(3)C++也可以使用类中类

#include<iostream>
using namespace std;
class Empty
{
public:
    class test
    {
    public:
        void print()
        {
            cout << "Empty::test::print" << endl;
        }
    };
    test t;
};

void main()
{
    Empty e;
    e.t.print();
}
条款40:明智而审慎的使用多重继承

(1)多重继承容易导致歧义,以及钻石型多重继承,导致子类拥有多个父类的成员。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值