2013年08月09日

C++ 转型操作符


阅读Scott Meyers的Effective C++和More Effective C++时,发现有两个条款的内容在讲述C++的转型操作,Effective C++的条款27:尽量减少做转型动作和More Effective C++的条款2:最好使用C++转型操作符,前一个条款强调转型会破坏类型系统,可能导致种类的麻烦,后一个条款强调若要转型,则最好使用新式的,即C++的转型操作!这儿将两片文章总结在一起!

C++转型操作

传统C语言的转型操作大致是(T)expression 或者 T(expression),两种形式并无差别,只是括号位置不同,C++则提供了四种新式的转型方式:

  1. const_cast<T>(expression)
  2. dynamic_cast<T>(expression)
  3. static_cast<T>(expression)
  4. reinterpret_cast<T>(expression)

这四种转型方式各有不同。

const_cast

const_cast转型用来改变表达式中的常量性(constness)或变易性(volatileness)

通过这个转型符唯一打算改变的是某物的常量性或变易性,其他的尝试则会失败。

class Widget { ... };
class SpecialWidget: public Widget{ ... };
void update(SpecialWidget *psw);
SpecialWidget sw; // sw是一个non-const object
const SpecialWidget& csw = sw; // csw是一个代表sw的引用,并且是一个const object

update(&csw); //错误!不能将const SpecialWidget* 传给一个需要SpecialWidget* 的函数
update(const_cast<SpecialWidget*>(&csw)); // 正确!csw的常量性被去除了。

Widget *pw = new SpecialWidget();
update(pw); //错误!pw的类型是Widget*,函数需要的是SpecialWidget*
update(const_cast<SpecialWidget*>(pw));//错误!const_cast只能用来转型常量性和变易性
dynamic_cast

dynamic_cast转型用来决定某对象是否归属于继承体系中的某个类型!

dynamic_cast即用来执行继承体系中安全的向下转型或跨系转型动作,也就是说可以利用dynamic_cast将指向base class objects的pointers或references转型为指向derived class objects的pointers 或 references,并且得知是否转型成功,转型失败则返回一个空指针(当转型对象是指针时)或者抛出一个异常(当转型对象是引用时)。并且你要知道,dynamic_cast 只能用于继承体系中,并且需要存在虚函数的类型上。

还需要明白的一点是dynamic_cast是唯一 可能耗费重大成本的转型动作!在Effective C++ 条款26中写道, > dynamic_cast的许多实现版本执行速度相当慢。例如至少有一个很普遍的实现版本基于“Class 名称之字符串比较”,如果你在四层深的单继承体系内的某个对象身上执行dynamic_cast,刚才说的那个实现版所提供的每一次dynamic_cast可能会耗费多达四次的strcmp调用,用以比较class名称。

static_cast

static_cast用来强迫隐式转换,例如将non-const转换成const,将int转换成double等等。

注意,若要将const转换成non-const只能使用const_cast。

reinterpret_cast

reinterpert_cast用来执行低级转型,实际动作及结果取决于编译器,这表示不可移植。

其最常用的用途是转换“函数指针”类型。例如有个数组都存储的是函数指针,尤特定类型:

typedef void (*FuncPtr)(); // FuncPtr是个指针,指向某函数
FuncPtr funcPtrArray[10]; // 数组每个元素都是函数指针

可能你希望将以下函数的一个指针放进上面的数组中

int doSomething();

若没有转型,这做不到!因为doSomething的类型与funcPtrArray接受的不一样,该数组含的函数指针返回值为void,这个却返回int.

funcPtrArray[0] = &doSomething; //错误!
funcPtrArray[0] = reinterpret_cast<FuncPtr>(&doSomething); // OK!
小结

上面提到了C++的四种转型方式,每种都有不同的应用场景,但是这些C++的转型方式有几个好处是:

1.很容易在代码中被辨识出来,人工看,或者使用grep! 每次转型都能精确的指明意图! 2.各转型动作的目标愈狭窄,编译器愈可能诊断出错误的运用!

若编译不支持这些转型动作,可以使用宏来代替。

  1. #define static_cast(TYPE,EXPR) ((TYPE)(EXPR))
  2. #define reinterpret_cast(TYPE,EXPR) ((TYPE)(EXPR))
  3. #define const_cast(TYPE,EXPR) ((TYPE)(EXPR))

这儿列出这种宏的方式只想让大家有些启发!

尽量少做转型

条款中介绍,C++规则的设计目标之一是,保证“类型错误”绝不可能发生。转型(casts)破坏类型系统(type system),可能导致种类的麻烦。转型并不是简单的告诉编译器把某种类型视为另一种类型,任何一个类型转换(显示或隐式)往往真令编译器编译出运行期间执行的码,书中举例将int转成double,可能会产生一些代码,因为计算器处理double和int的底层完全不一样!

还给出了另一个震撼的例子。

class Base{ ... };
class Derived: public Base{ ... };
Derived d;
Base* pb = &d;

上面建立了一个base class指向一个dervied class对象,但有时候上述的两个指针值并不相同。这种情况下会有一个偏移量在运行期间被施于Derived* 指针上,用以取得正确的Base* 指针,这表明,单一对象可能拥有一个以上的地址。文中还举了另一个例子,在许多应中要求在derived class内的irtual函数代码的第一个动作就是调用base class的对应函数。看下面的代码:

class Window{
    public:
        virtual void onResize(){...}
};

class SpeicalWindow: public Window{
    virtual void onResize(){
        static_cast<Window>(*this).onResize();
        ....
    }
};

注意转型的那一行! 该行代码,将*this转型成window,对函数onResize的调用因此调用了Window::onResize().但是,恐怕没想到的,它调用的并不是当前对象上的函数, 对函数onResize的调用并不是当前对象上的函数,而是稍早转型动作所建立的一个*this对象之base class成分的暂时副本身上的onResize!虽说都是函数,只是不同对象上的函数,关键就在于成员函数都有一个隐藏的this指针,会影响成员函数操作的数据。这行代码是在当前对象之base class成分的副本上调用window::onResize(),然后在当前对象上执行SpecialWindow的专属动作,如果Window::onResize修改了对象的内容,当前对象的内容则没有改动,实际上是改动的那个副本。问题就是这儿了! 正确的方式是Window::onResize();替换上面的那一行!

Scott Meyers建议我们应该尽可能的隔离转型动作,通常是把它隐藏在某个函数内,函数的接口会保护调用者不受函数内部任何肮脏龌䟶的动作影响。客户可以随后调用这些函数,而不需要将转型放进他们自己的代码内。

补充

今天看到一些关于C++类型转换的,发现之前的写的这篇博文还不能很好的解释清楚这些类型转换,上面的内容强调的是转型的原则,如何将这些转型用在正确的场合,但是没有强调这些转型到底可以用在多广的范围。先来看看转型的实际例子如下:

class A{
    public:
        A():a(2){}
        virtual void foo(){
            cout << "A" << endl;
        }
        int a;
};

class B{
    public:
        B():b(2){}
        virtual void foo(){
            cout << "B" << endl;
        }
        int b;
};

class C: public A, public B{
    public:
        virtual void foo(){
            cout << "C" << endl;
        }
};

void bar1(A& pa){
    B* pc = dynamic_cast<B*>(pa);
}

void bar2(A* pa){
    B* pc = static_cast<B*>(pa);
}

void bar3(){
    C c;
    //A *pa = &c;
    A *pa = new A;
    B *pb = static_cast<B *>(static_cast<C*>(pa));
    delete pa;
}

看上面这三个转换,是否存在问题,如果通过编译,若能编译是否能运行。

1.第一和第三个函数可以通过编译,运行;第二个则存在问题。 2.首先参考百度百科关于dynamic_cast 3.再参看百度百科关于static_cast

dynamic_cast 归纳

1.该转型只能转换类的指针,类的引用或者void*; 2.该转型必须要指向的类中必须存在虚函数。 3.该转型还可以用在类之间的交叉转换(这就说明了函数一为什么可以转换A与B两个无关联的类了)

static_cast 归纳

1.该转型可以用于类层次结构中基类和派生类之间指针或引用的转换。(向上安全,向下可行,但不安全) 2.用于基本数据类型之间的转换 3.把空指针转换成目标类型的空指针(注意:是空指针到空指针的转换) 4.把任何类型的表达式转换成void类型

前一篇: Nova Service启动 后一篇: Openstack开发者入门(二)