• More Effective C++ 条款33 将非尾端(non-leaf classes)设计为抽象类(abstract classes)


    1. 假如程序有一个Chicken class,Lizard class,Animal class,其中Chicken class和Lizard class继承自Animal class,整个继承体系像这样:

        Animal负责具体化所有东吴的共同特征,Lizard和Chicken是需要特殊对待的两种动物,它们的简化定义像这样:

    class Animal {
    public:
        Animal& operator=(const Animal& rhs);
        ...
    };
    class Lizard: public Animal {
    public:
        Lizard& operator=(const Lizard& rhs);
        ...
    };
    class Chicken: public Animal {
    public:
        Chicken& operator=(const Chicken& rhs);
        ...
    };
    View Code

        那么也就允许这样的代码:

    Animal* pAnimal1;
    Animal* pAnimal2;
    ...
    *pAnimal1=*pAnimal2;
    View Code

        由于Animal*具有多态性,也就是说它实际所指向的类型是不确定的,因此*pAnimal1和*pAnimal2的动态类型不同而导致operator=被错用的情况也可能出现,类的设计者有责任对这种赋值行为进行检查或避免"异型赋值"的出现.

        最直接的方法是将operator=设为virtual,像这样:

    class Animal {
    public:
        virtual Animal& operator=(const Animal& rhs);
        ...
    };
    class Lizard: public Animal {
    public:
        virtual Lizard& operator=(const Animal& rhs);
        ...
    };
    class Chicken: public Animal {
    public:
        virtual Chicken& operator=(const Animal& rhs);
        ...
    };
    View Code

        C++要求虚函数原型相同,尽管允许函数返回引用时可以返回派生类引用,但函数参数仍然必须完全相同.这就要求在Lizard和Chicken的operator=内部使用dynamic_cast,Lizard的operator=的定义像这样:

    Lizard& Lizard::operator=(const Animal& rhs)
    {
        //将rhs转为const Lizard&,失败则抛出异常
        const Lizard& rhs_liz = dynamic_cast<const Lizard&>(rhs);
        proceed with a normal assignment of rhs_liz to *this;
    }
    View Code

        由于同型赋值实际上不需要dynamic_cast,因此opeator=的实现可以升级成这样:

    class Lizard: public Animal {
    public:
        virtual Lizard& operator=(const Animal& rhs);
        Lizard& operator=(const Lizard& rhs); //新增的non-virtual函数
        ...
    };
    Lizard& Lizard::operator=(const Animal& rhs)
    {
        return operator=(dynamic_cast<const Lizard&>(rhs));
    }
    View Code

        当使用dynamic_cast有其固有的缺点:dynamic_cast可能抛出异常,也就是说编译器会产生额外代码用于"待命捕捉bad_cast exceptions,并作某些合理应对",因此会产生一定的效率损失;此外错误只有在运行期才能检查出来.因此应该考虑其他策略.

        阻止类似于*pAnimal1=*pAnimal2的行为的最直接办法就是将Animal的operator=设为private,但这样一来Animal的自身赋值也不允许,而且Lizard和Chicken的operator=也无法实现,因为它们默认调用Animal的operator=,即使将Animal的operator=设为protected,前一个问题仍然存在.最简单的办法就是消除Animal对象相互赋值的需要,也就是将Animal设为抽象基类,但又由于Animal可能必须作为一个具体类,因此另一个策略就是使Lizard和Chicken不再继承自Animal,并使得Lizard,Chicken,Animal继承自一个更痛的抽象基类AbstactAnimal,整个继承体系像这样:

        AbstractAnimal,Lizard,Animal,Chicken的定义像这样:

    class AbstractAnimal {
    protected:
        AbstractAnimal& operator=(const AbstractAnimal& rhs);
    public:
        virtual ~AbstractAnimal() = 0; // 虽然AbstractAnimal的析构函数被设为pure virtual,但仍然需要提供定义
        ...
    };
    class Animal: public AbstractAnimal {
    public:
        Animal& operator=(const Animal& rhs);
        ...
    };
    class Lizard: public AbstractAnimal {
    public:
        Lizard& operator=(const Lizard& rhs);
        ...
    }
    class Chicken: public AbstractAnimal {
    public:
        Chicken& operator=(const Chicken& rhs);
        ...
    };
    View Code

        采用这种策略直接禁止了像*pAbstractAnimal1=*pAbstractAnimal2的操作,而仍然像*pAnimal1=*pAnimal2的操作并且在编译时检查类型.

    2. 1的分析过程实际上体现了这样一种思想:当具体类被当做基类使用时,应该将具体类转变为抽象基类.

        然而有时候需要使用第三方库,并继承其中一个具体类,由于无法修改该库,也就无法将该具体类转为抽象基类,这是就需要采取其他选择:

        1). 继承自现有的具体类,但要注意1所提出的assignment问题,并小心条款3所提出的数组陷阱.

        2). 试着在继承体系中找一个更高层的抽象类,然后继承它.

        3). 以"所希望继承的那么程序库类"来实现新类.例如使用复合或private继承并提供相应接口.此策略不具灵活性.

        4). 为"所希望继承的那么程序库类"定义一些non-member,不再定义新类.

  • 相关阅读:
    js使用笔记
    rabbit-mq使用官方文档
    tomcat Enabling JMX Remote
    Venom的简单使用
    Random模块
    时间模块
    shulti模块简述
    Python的os模块
    Python压缩及解压文件
    Kali的内网穿透
  • 原文地址:https://www.cnblogs.com/reasno/p/4876064.html
Copyright © 2020-2023  润新知