item27:尽量少做转型动作

本篇文章引用材料:

Scott Meyers<<Effective C++>>第三版---侯捷译

      

     C++规则的设计目标之一是,保证“类型错误”绝不可能发生。然而,转型破坏了类型系统。那可能导致任何种类的麻烦,有些容易辨识,有些非常隐晦。在C++中转型是一个你会想带着极大尊重去亲近的一个特性。


     C风格的转型动作(旧式转型):

     (T)expression         //将expression 转型为T

     函数风格转型动作:

     T(expression)      //将expression 转型为T


     C++提供的四种新式转型(常被称为new-style或C++-style casts):

     const_cast<T>(expression)

     dynamic_cast<T>(expression)

     reinterpret_cast<T>(expression)

     static_cast<T>(expression)

     各有不同的目的:

    const_cast通常被用来将对象的常量性转除(cast away the constness)。它也是唯一有此能力的C++-style转型操作符。

    dynamic_cast注要用来执行“安全向下转型”(safe downcasting),也就是用来决定某对象是否归属继承体系中的某个类型。它是唯一无法由旧式语法执行的动作,也是唯一可能耗费重大运行成本的转型动作。dynamic_cast运算符可以在执行期决定真正的类型。

    reinterpret_cast取决于编译器,也就是表示它不可移植。

    static_cast 用来强迫隐式转换(implicit conversions),如将int转换为double,将non-const对象转换为const对象等等。

  

    旧式转型仍然合法,但新式转型较受欢迎。原因是:1. 它们很容易在代码中被辨识出来(不论是人工识别或使用工具如grep),因而得以简化“找出类型系统在哪个地点被破坏”的过程。2. 各转型动作的目标愈窄化,编译器愈可能诊断出错误的运用。举个例子,如果你打算将常量性去掉,除非使用新式转型的const_cast否则无法通过编译。

    许多程序员相信,转型其实什么都没做,只是告诉编译器把某种类型视为另一种类型。这是错误的观念。任何一种类型转换(不论是通过转型操作而进行的显式转换,或通过编译器完成的隐式转换)往往真的令编译器编译出运行期间指行的码。

    class Base{.....};

    class Derived:public Base{......};

    Derived d;

    Base *pb = &d;    //隐喻地将Derived* 转换为Base*

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

    上个例子表明,单一对象(例如一个类型为Derived的对象可能拥有一个以上的地址,实际上一旦使用多重继承,这事几乎一直发生着。即使在单一继承中也可能发生。)

    这里我写个程序解释下,相关知识可参考,Stanley B.Lippman<<Inside The C++ Object Model>>----侯捷译,相关章节第三章 Data语意学。

class Base1{   
public:
        int b1;
};
class Base2{
public:

        int b2;
};
class Derived:public Base1,public Base2{
public:

};
int main(int argc,char* argv[]){    

Derived d;


Base1 *pb1 = &d;
Base2 *pb2 = &d;

cout<<"&d =  "<<&d<<endl;
cout<<"pb1 = "<<pb1<<endl;
cout<<"pb2 = "<<pb2<<endl;

system("Pause.");    
return 0;
}

运行程序观察,可发现d 和pb1的地址是一样的。

   pb1 = &d;

//虚拟的C++码

    pb1 = (Base1*)((char*)&d) ;

    

   pb2 = &d;

//虚拟的C++码

   pb2 = (Base2*)((char*)&d + sizeof(Base2));

   

    另一件关于转型动作的有趣事情是:我们很容易写出某些似是而非的代码(在其他语言也许真是对的)。例如许多应用框架(Application frameworks)都要求derived class内的virtual函数代码的第一个动作就先调用base class的对应函数。假设我们有个Window base class和一个 SpecialWindow derived class,两者都定义了virtual函数onResize。进一步假设SpecialWindow的OnResize函数被要求首先调用Window的OnResize。下面是实现方式之一,它看起来对,但实际上是错误的:

class Window{
public:
	virtual void OnResize(){cout<<"Window::OnResize()\n";}
};
class SpecialWindow:public Window{
public:
	 void OnResize(){
		 static_cast<Window>(*this).OnResize();//
		 cout<<"SpecialWindow::OnResize()\n";
	 }
};
int main(int argc,char* argv[]){    

	Window *pw;
	SpecialWindow sp;

	pw = &sp;
	pw->OnResize();

	system("Pause.");    
	return 0;
}

        一如你所预期的,这段程序将*this转型为Window,对函数OnResize的调用也因此调用了Window::OnResize。但恐怕你没有想到的,它调用的并不是当前对象上的函数,而是稍早转型动作所建立的一个"*this 对象之base class成分"的暂时副本身上的OnResize!(译注:函数就是函数,成员函数只有一份,"调用其那个对象身上的函数"有什么关系呢?关键在于成员函数都有个隐藏的this指针,会因此影响成员函数操作的数据。)在说一次,上述代码并非在当前对象身上调用Window::OnResize之后又在该对象身上执行SpecialWindow专属动作。不,它是在“当前对象之base class成分”的副本上调用Window::OnResize,然后在当前对象身上执行SpecialWindow专属动作。如果Window::OnResize修改了对象内容(不能说没有可能性,因为OnResize是个non-const成员函数),当前对象其实没被改动,改动的是副本。然而SpecialWindow::OnResize内如果也修改对象,当前对象真的会被改动。这使当前对象进入一种“伤残”状态:其base class成分更改没有落实,而derived class成分更改倒是落实了。

    可写程序测试:

class Window{
private:
	int w;
public:
	Window(int ww):w(ww){ }
	virtual void OnResize(){
		w = 1111111;
		//cout<<"Window::OnResize()\n";
	}
	int getw(){return w;}
};
class SpecialWindow:public Window{
private:
	int s;
public:
	SpecialWindow(int ww,int ss):Window(ww),s(ss){ }
	 void OnResize(){		 
		 static_cast<Window>(*this).OnResize();
		 s = 222222;
		// cout<<"SpecialWindow::OnResize()\n";
	 }
	 int gets(){ return s;}
};
int main(int argc,char* argv[]){    

	
	SpecialWindow sp(1,2);
	cout<<"Before cast: \n";
	cout<<"w = "<<sp.getw()<<endl;;
	cout<<"s = "<<sp.gets()<<endl;
	cout<<"After cast:\n";
	sp.OnResize();
	cout<<"w = "<<sp.getw()<<endl;;
	cout<<"s = "<<sp.gets()<<endl;

	system("Pause.");    
	return 0;
}

static_cast<Window>(*this)相当于旧式转换的Window(*this),这下明白了吧,  Window(*this)将调用Window的copy构造函数,创造出一个临时对象来!你可以试着往Window添加copy构造函数和析构函数就会明白了。

   解决之道:

class SpecialWindow:public Window{
public:
void OnResize(){
Window::OnResize();// 调用 Window::OnResize()作用于*this身上
cout<<"SpecialWindow::OnResize()\n";
}
};

    

      在探究dynamic_cast设计意涵之前,值得注意的是,dynamic_cast的许多实现版本指向速度相当慢。例如至少有一个很普遍的实现版本基于“class名称之字符串比较”,如果你四层深的单继承体系内的某个对象身上执行dynamic_cast,刚才说的那个实现版本所提供的每一次dynamic_cast可能会耗用多达四次的strcmp调用,用于比较class名称。深度继承或多重继承的成本更高!某些实现版这样做有原因(它们必须支持动态链接)。然而我还是要强调,除了对一般转型保持机敏与猜疑,更应该在注重效率的代码中对dynamic_cast保持机敏与猜疑。




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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值