Effective C++条款27:实现——尽量少做转型动作

一、数据类型转换语法

C语言风格的类型转换格式

C++新式转型

二、建议使用C++新式转型

  • 旧式转型虽然虽然合法,但是程序设计时应该尽量使用新式转型。原因如下:
    • ①新式转型在代码中很容易被识别出来
    • ②新式转型使转型动作的目标比较有限。例如你打算将常量性移除,那么只能只用const_cast

旧式转型的使用

  • 唯一使用旧式转型的场景就是,调用一个explicit构造函数将一个对象传递给一个函数时
  • 例如:
class Widget {

public:
    explicit Widget(int size);
    ...
};

void doSomeWork(const Widget& w);
doSomeWork(Widget(15));                //函数风格
doSomeWidget(static_cast<Widget>(15)); //c++风格

 

三、对转型的一种误解

  • 很多人认为转型其实什么都没做,只是告诉编译器把某种类型视为另一种类型而已,其实这是错误的观念
  • 任何一种类型转换(不论是显式还是隐式,或是编译器自身完成的隐式转换),编译器在编译出运行期间执行的代码时都要做相应的改变

演示案例

  • 下面代码将int类型转换为double类型,x的存储结构肯定会改变
int x, y;

double d = static_cast<double>(x) / y;

 

演示案例

  • 上面将派生类转换为基类,但有时上述两个指针值并不相同。这时候会有个偏移量在运行期被施行于Derived*指针身上,用以取得正确的Base*指针值
class Base {};

class Derived :public Base {};


int main()

{

    Derived d;

    Base* pb = &d; //派生类转换为基类


    return 0;

}

 

四、关于转型的一个错误演示案例

  • 例如许多应用框架都要求在派生类的virtual函数中第一个动作就是先调用基类的对应函数。例如下面我们有一个Windows类以及它的派生SpecialWindow,其中onResize()是虚函数
class Window {

public:

    virtual void onResize() {}

};


class SpecialWindows :public Window {

public:

    virtual void onResize() {

        //尝试调用基类的虚函数,语法虽然没错,但是逻辑是错误的(程序会产生不明确行为)

        static_cast<Window>(*this).onResize();

    }

};
  • 为什么上面的类型转换是错误的:
    • 我们在函数中将*this转换为基类类型,然后尝试调用onResize()虚函数,但是该转型动作产生的实际上是一个“this对象之基类成分”的一个副本,然后在这个副本上调用onResize函数
    • 所以当在副本上面调用onResize()函数对于this对象根本没有任何的影响,于是客户端程序以为调用了onResize()函数使对象本身改变了,但是实际上没有改变,只是改变了一个临时对象而已
  • 替代方案:就是在虚函数中显式的调用基类的函数。例如将上面派生类的虚函数更改为下面的形式就是对的了
class SpecialWindows :public Window {

public:

    virtual void onResize() {

        Window::onResize(); //调用Window::onResize作用于*this身上

    }

};

五、关于dynamic_cast的一些概述

  • dynamic_cast主要用于:想要使用派生类的方法,但是此时只有一个基类的指针/引用,此时可以使用该类型转换将基类的指针转换为派生类指针
  • 为什么不建议使用该转型:
    • 因为该转换会使代码执行的非常慢
    • 有的编译器对于该转型的实现原理是“基于class名称的字符串比较”,也就是说如果在继承体系很多的对象身上应用这个类型转换,那么会有很多的strcmp的调用来比较class名称
  • 下面是一个dynamic_cast的演示案例,只有派生类有一个闪烁效果的blink()函数,此时我们想用基类指针访问这个函数
class Window {

public:

    //...

};


class SpecialWindows :public Window {

public:

    void blink();

};



int main()

{

    std::vector<std::tr1::shared_ptr<Window> > VPW;


    //遍历容器中的每个Window对象并且调用blink函数
    
    for (auto iter = VPW.begin(); iter != VPW.end(); ++iter) {

        //使用dynamic_cast转型,get()函数是获取shared_ptr中的Window对象指针的意思

        if (SpecialWindows* psw = dynamic_cast<SpecialWindows*>(iter->get())) {

            psw->blink();

        }

    }

    return 0;

}

替代dynamic_cast方法①

  • 对于上面的演示案例我们给出了下面的一种替代方法:直接使用容器存储派生类对象,不存储基类对象
  • 缺点:但是这种方法可能需要为每一种派生类都创建一个容器来存储
int main()

{

    std::vector<std::tr1::shared_ptr<SpecialWindows> > VPW;


    for (auto iter = VPW.begin(); iter != VPW.end(); ++iter) {

        (*iter)->blink();
    
    }

    return 0;

}

替代dynamic_cast方法②

  • 第二种方法是:当你在基类中想做一些事情,那么也就基类中同时声明一份,并且将函数设置为virtual的(但是基类中的虚函数什么都不做)

  • 代码如下:


class Window {

public:

    virtual void blink() { //条款34告诉你缺省实现代码可能是个馊主意

        //什么都不做

    }
    
};


class SpecialWindows :public Window {

public:

    virtual void blink() {

        //实现相关代码

    }

};


int main()

{

    std::vector<std::tr1::shared_ptr<Window> > VPW;


    for (auto iter = VPW.begin(); iter != VPW.end(); ++iter) {

        (*iter)->blink();

    }

    return 0;

}

 

  • 绝对必须避免的一件事情就是所谓的“连串dynamic_cast”,例如下面的代码,下面的代码运行又大又慢,而且基础不稳定,因为每次Window类继承体系一旦改变,所有的代码都需要再次更改
class Window {};


class SpecialWindows :public Window {};

class SpecialWindows2 :public Window {};

class SpecialWindows3 :public Window {};

//...


int main()

{

    std::vector<std::tr1::shared_ptr<Window> > VPW;


    for (auto iter = VPW.begin(); iter != VPW.end(); ++iter)

    {

        if (SpecialWindows *psw1 = dynamic_cast<SpecialWindows*>(iter->get())) {

            //...

        }

        else if (SpecialWindows2 *psw2 = dynamic_cast<SpecialWindows2*>(iter->get())) {

            //...

        }

        else if (SpecialWindows3 *psw3 = dynamic_cast<SpecialWindows3*>(iter->get())) {
    
            //...

        }

        //...

    }

    return 0;

}

五、总结

  • 如果可以,尽量避免转型,特别是在注重效率的代码中避免dynamic_cast。如果有个设计需要转型动作,试着发展无需转型的替代设计
  • 如果转型是必须的,试着将它隐藏于某个函数背后。客户随后可以调用该函数,而不需要将转型放进它们自己的代码
  • 宁可使用新式的转型,不要使用旧式转型。前者很容易辨别出来,而且也比较有着分门别类的职掌
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值