• effective C++ 条款 27:尽量少做转型动作


    转型(casts)破坏了类型系统(type system)。可能导致任何类型的麻烦。

    c++提供四种新式转型

    const_cast<T>(expression) //cast away the constness

    dynamic_cast<T>(expression) //safe downcasting安全向下转型

    reinterpret_cast<T>(expression)//意图执行低级转型,实际动作(及结果)可能取决于编译器,这也表示它不可移植;低级代码以外很少见 

    static_cast<T>(expression)//用来强迫隐式转换(implicit conversions)

    c风格旧式转型

    (T)expression

    T(expression)

    新式转型比较受欢迎,1,很容易在代码中被辨别出来;2,各种转型动作愈窄化,编译器愈能诊断出错误的运用。

    唯一使用旧式转型的时机是当调用一个explicit构造函数将一个对象传递给一个函数时:

    class Widget{
    public:
        explicit Widget(int size);
        ...
    };
    void doSomeWork(const Widget &w);
    doSomeWork(Widget(15));//函数风格的转型动作创建一个Widget
    doSomeWork(static_cast<Widget>(15));//c++风格的转型动作创建一个Widget对象

    蓄意的“对象生成”动作不怎么像“转型”,很可能使用函数风格的转型动作,而不使用static_cast。但有时最好忽略你的感觉,始终理智的使用新式转型。

    任何一个类型转换(不论是通过转型动作而进行的显示转换还是通过编译器完成的隐式转换)往往真的令编译器编译出运行期间执行的码

    int x, y;
    ...
    double d = static_cast<double>(x)/y;//使用浮点数除法

    将int 转换成double几乎肯定会产生一些码,因为大部分计算机体系结构中,int的底层表述不同于double的地层表述。

    class Base{...};
    class Derived : public Base{...};
    Derived d;
    Base* pb = &d;//隐喻的将derived*转换成Base*

    这里我们只是建立一个base class指针指向一个derived class对象,但有时候上述的两个指针值并不相同。这种情况下会有个偏移量在运行期被施行于Derived*指针身上,用于取得正确的Base*指针值。

    上述例子表明,单一对象(例如一个类型为Derived的对象)可能拥有一个以上的地址(例如“以Base*指向它”时的地址和以“Derived*指向它”时的地址)。c,java,c#不可能发生这种事,但c++可能!实际上一旦使用多重继承,这事几乎一直发生着。即使是在单一继承中也可能发生。虽然这还有其他意涵,但至少意味着你通常应该避免做出“对象在c++中如何布局”的假设,更不应该以此假设为基础执行任何转型动作。例如将对象地址转型成char*指针,然后在他们身上进行指针运算,几乎总是导致无意义(不明确)行为。

    对象的布局方式和他们的地址计算方式随编译器的不同而不同,那意味着“由于知道对象如何布局”而设计的转型,在某一平台行的通,在其他平台并不一定行得通。

    另一件关于转型的有趣事情是:我们很容易写出某些似是而非的代码(其他语言中也许是对的)。例如SpecialWindow的onResize被要求首先调用Window的onResize。下面是看起来对,实际上错:

    class Window{
    public:
        virtual void onResize(){...}
        ...
    };
    class SpecialWinddow:public Window{
    public:
        virtual void onResize(){
           static_cast<Window>(*this).onResize();//将*this转换成Window,然后调用其onResize;这样不行!
            ...
        }
    };

    它调用的并不是当前对象上的函数,而是稍早转型动作所建立的一个“*this对象的base class成分”的暂时副本身上的onResize!并不是在当前对象身上调用Window::onResize之后又在该对象上执行SpecialWindow专属行为。不,它是在“当前对象之base calss成分”的副本上调用Window::onResize,然后在当前对象上执行SpecialWindow专属动作。如果Window::onResize修改了对象内容,当前对象其实没被改动,改动的是副本。然而SpecialWindow::onResize内如果也修改对象,当前对象真的会被改动。这使当前对象进入一种“伤残”状态:其base class成分的更改没有落实,而derived class成分的更改倒是落实了。

    真正的解决方法是:

    class SpecialWinddow:public Window{
    public:
        virtual void onResize(){
            Window::onResize();//调用Window::onResize作用于*this身上
            ...
        }
    };

    dynamic_cast的许多实现版本执行速度相当慢。假如至少有一个很普通的实现版本基于“class名称之字符串比较”,深度继承或多重继承的成本更高!某些实现版本这样做有其原因(它们必须支持动态链接)。在对注重效率的代码中更应该对dynamic_cast保持机敏猜疑

    之所以要用dynamic_cast,通常是因为你想在一个你认定为derived class对象身上执行derived class操作函数,但你的手上只有一个“指向base”的pointer或者reference,你只能靠他们来处理对象。两个一般性做法可以避免这个问题:

    一,使用容器,并在其中存储直接指向derived class 对象的指针(通常是智能指针),如此便消除了“通过base class接口处理对象”的需要。假设先前的Window/SpecialWindow继承体系中有SpecialWindows才支持闪烁效果,试着不要这样做:

    class Window{...};
    class SpecialWindow:public Window{
    public:
        void blink();
    };
    typedef std::vector<std::tr1::shared_ptr<Window>> VPW;
    VPW winPtrs;
    for (VPW::iterator iter = winPtrs.begin(); iter != vinPtrs.end(); ++iter)
    {
        if(SpecialWindow* psw = dynamic_cast<SpecialWindow*>(iter->get()))
            psw->blink();
    }

    应该改而这样做:

    typedef std::vector<std::tr1::shared_ptr<SpecialWindow>> VSPW;
    VSPW winPtrs;
    for (VSPW::iterator iter = winPtrs.begin(); iter != vinPtrs.end(); ++iter)
    {
        (*iter)->blink();
    }

    当然,这种做法无法在同一个容器内存储指针“指向所有可能之各种Window派生类”。如果需要处理多种窗口类型,你可能需要多个容器,他们都必须具备类型安全性(type-safe)。

    另一种做法让你通过base class接口处理“所有可能之各种window派生类”,那就是在base class 里提供virtual函数做你想对各个Window派生类做的事。虽然只有SpecialWindows可以闪烁,但或许将闪烁函数声明于base class内并提供一份什么也不做的缺省版本是有意义的:

    class Window{
    public:
        virtual void blink(){}
    };
    class SpecialWindow:public Window{
    public:
        virtual void blink(){...};
    };
    typedef std::vector<std::tr1::shared_ptr<Window>> VPW;
    VPW winPtrs;
    for (VPW::iterator iter = winPtrs.begin(); iter != vinPtrs.end(); ++iter)
    {
        (*iter)->blink();
    }

    无论哪一种写法,并非放之四海皆准,但在许多情况下它们都提供一个可行的dynamic_cast替代方案。当它们有此功效时,你应该欣然拥抱它们。

    绝对必须拒绝的是所谓的“连串(cascading)dynamic_casts”:

    typedef std::vector<std::tr1::shared_ptr<Window>> VPW;
    VPW winPtrs;
    for (VPW::iterator iter = winPtrs.begin(); iter != vinPtrs.end(); ++iter)
    {
        if (SpecialWindow1 * psw1 = dynamic_cast<SpecialWindow1*>(iter->get())){...}
        if (SpecialWindow2 * psw1 = dynamic_cast<SpecialWindow2*>(iter->get())){...}
        if (SpecialWindow3 * psw1 = dynamic_cast<SpecialWindow3*>(iter->get())){...}
    }

    这样的代码应该总是以某些“基于virtual函数调用”的东西取而代之。

    优良的c++代码很少使用转型,我们应该尽可能隔离转型动作,通常是把它隐藏在某个函数内,函数的接口会保护调用者不受函数内部任何肮脏龌龊的动作的影响。

  • 相关阅读:
    Java使用jxl修改现有Excel文件内容,并验证其是否对公式的结果产生影响
    Java使用MyBatis的ScriptRunner执行SQL脚本
    Linux下批量解压.Z格式文件
    Java中将一个反斜杠转换成两个反斜杠
    mysql线上操作常用命令
    MySQL主从不一致修复
    slave_exec_mode参数对主从复制的影响
    ssh访问跳过RSA key"yes/no"验证
    k8s更新Pod镜像
    Go 延迟函数 defer 详解
  • 原文地址:https://www.cnblogs.com/lidan/p/2335717.html
Copyright © 2020-2023  润新知