尽量少做转型动作 《Effective C++》

背景:

C++规则的设计目标之一:保证 类型错误 绝不可发生。(非常重要!)

一个程序能干净地通过编译,表示:该程序不会在任何对象身上执行 任何不安全,无意义、愚蠢的操作。

但转型破坏类型系统。可能会导致麻烦。

c++提供的转型方案:

const_cast<T>(expression)

dynamic_cast<T>(expression)

reinterpret_cast<T>(expression)

static_cast<T>(expression)

各自分工不同:

const_cast<T>(expression):将对象的常量性转除。意思就是将一个对象类型是const的,经过该转换之后,就不是const类型的。

dynamic_cast<T>(expression):用于执行安全向下类型转换。意味:决定某对象是否归属继承体系中的某个类型。也是唯一可能消耗重大运行成本的转型动作。

reinterpret_cast<T>(expression):执行低级转型。实际转型动作以及结果可能取决于编译器。这表示它不可移植。比如将指向int型的指针,转型为int类型。很少见~~~

static_cast<T>(expression):强迫隐式转换。比如:将非const类型转型为const,将int转为double类型等。但无法将const类型转型为非const类型,只有const_cast可以实现


为何少用或不用转型?

1. 隐式转型或显示转型,有时候需要一个偏移量来获取到转型后的对象。

示例:

class Base{ ... };

class Derived:public Base{ ... };

Derived d;

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

Base* pb = static_cast<Derived*>d;//显示类型转换

此处目的:将一个基类指针指向一个派生类对象。

编译器实际执行的动作:

这种情况下会有个偏移量在运行期被施行于Derived* 指针身上,用于取得正确的Base*类型指针值。

意思就是:

Base* pb指向的地址是 Derived* d指针的地址经过一个offset偏移量计算得到。此时的&d的地址代表的是Base 在Derived类中的偏移位置。

但是&d本身有个地址,是指向自身Derived的。

这也就出现一个指针,有可能有多个不同的地址。这就是C++。 其他语言是不可能发生这种情况的。

因此:

在这种情况下,不要将对象地址转型为其他类型的指针,比如char*,然后进行指针算术,会导致无定义行为。



2. 不要欺骗编译器,请真实表达自己想做的事情(你好我好大家好)。

示例:
目的:B调用method方法之前,必须调用A方法的method。
class A{
public:
virtual void method(){ ... };   // 类A的实现方法
};
 
class B: public A{
public:
virtual void method()
{
static_cast<A>(*this).method();     //此处要求:B在调用method方法之前,必须先调用A的method方法。
//此处将*this 转为A,然后调用A的method方法。
....   //B的专属方法实现
}
};

 案例解析
这段程序将*this转型为A,此时得到的A是*this对象的基类成分,是通过偏移量计算得到的A 。此时A是一个副本,通过副本调用method方法,最后执行B的专属动作。
此时如果A::method修改了对象内容(method不是const成员函数,可更改),当前对象其实并没有改变,改变的只是副本
然而B::method修改 了对象,当前对象就会被改变。这就造成:基类成分的更改没有落实,派生类成分的更改倒是落实更改。
这就是通过转型引入的问题
解决方案:
告诉编译器,你的真实的目的。不要转型。
如下:
class B: public A{
public:
virtual void method()
{
A::method();     //真实的想法:A::method作用于*this身上
....   //B的专属方法实现
}
};


3.dynamic_cast 的使用,更需谨慎
dynamic_cast 背景:
dynamic_cast的实现版本有很多,各自实现版本执行速度相当慢
举例:
有个普遍的实现版本是基于class类名称的字符串进行比较找到转型后的对象。 假如在一个四层单继承体系中,对某个对象身上执行dynamic_cast,
则每一次dynamic_cast可能会耗用多达四次的strcmp调用,用于比较名称。深度继承或多重继承的成本更高!!!
因此对于效率要求较高的代码中,使用dynamic_cast需要提高警惕。

替代使用dynamic_cast转型的场景?

背景:

想使用dynamic_cast转型,通常是想在派生类身上执行派生类成员函数,但当前环境中只有一个指向基类的指针或引用,只能靠dynamic_cast转型实现。

解决方案:

1. 使用容器并在其中存储直接指向派生类对象的指针(通常是智能指针)

举例:

class A{ ... };

class B: public A{

public:

void M();

};

先假设按照以下方法实现:

typedef std::vector< std::shared_ptr<A> > VPA;

VPA vPtrs;

....

for(VPA::itertor iter = vPtrs.begin();iter != vPtrs.end();++iter){

if(B* pb = dynamic_cast<B*>(iter->get())){

pb->M();

}

}

该方案就是直接使用dynamic_cast实现访问派生类的成员函数方法。但是这个弊端,上面已述:采用普遍的比较类名称来找到目标对象,耗费的成本太高,效率不好。


推荐使用方案:

typedef std::vector< std::shared_ptr<B> > VPB;

VPB vPtrs;

....

for(VPA::itertor iter = vPtrs.begin();iter != vPtrs.end();++iter){

(*iter)->M();

}


这基本就解决正确合理地调用派生类的方法,而不必使用类型转换。

存在的缺点:

该方案的容器无法存储指向所有可能的组合的A派生类的指针。意思就是A的其他派生类C,D,E,F等就不能通过上诉一个容器
typedef std::vector< std::shared_ptr<B> > VPB;搞定。

有多少个派生类型,就可能需要多个容器。并且具备类型安全。

2. 在基类提供virtual函数,一份什么也不实现的代码。

举例:

class A{

public:

virtual void M(){};//什么也不实现。

};


class B:public A{
public:
virtual void M(){ ... }; //该类实现具体业务
};

typedef std::vector<std::shared_ptr<A> > VPA;    //包含指向所有可能的A类型。有可能是B,有可能是C,D,E,F等
VPA vPtrs;
...
for(VPA::iterator iter = vPtrs.begin();iter != vPtrs.end();++iter){
(*iter)->M(); //此处没有dynamic_cast
}


总结:

1.如果可以,尽量避免转型。特别是在注重效率的代码中避免dynamic_cast操作。如果有设计需要转型动作,

尝试无需转型的设计方案:

A:使用容器并在其中存储直接指向派生类对象的指针(通常是智能指针)

B:在基类提供virtual函数,一份什么也不实现的代码。

2.隐式转型或显示转型,有时候需要一个偏移量来获取到转型后的对象

3.不要欺骗编译器,请真实表达自己想做的事情

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值