Effective c++ 学习笔记(item27)

2022-09-20  本文已影响0人  懒生活

类型转换,c++为什么又新增了4种方式,他们的初衷是啥?

c方式下传统的类型强制转换很简单,`(T)expression` 或者`T(expression)`就搞定了,简单明了。但是C++放弃了这种简单明了的方式,新增了4种方式

```

const_cast<T>(expression)

dynamic_cast<T>(expression)

reinterpret_cast<T>(exptession)

static_cast<T>(expression)

```

为什么推荐使用c++方式的转换,而不是使用传统c的方式。主要目的是为了方便维护和发现问题。如果你要走查整个工程所有的类型转换,使用新方式,你可以简单的用_cast检索出所有的位置。同时强制类型转换的类型很多,你可以按类型检索分析。并且避免类型转换的错误。

当你想进行类型转换的时候,根据特定的场景慎重选择4种转换函数的一种,这样会让你更慎重的对待类型转换。

# const_cast

这个的目的是明确告诉编译器要把一个const的对象,强制转换成非const类型,以方便修改。

# static_cast

这个的目的是明确告诉编译器这个地方的隐式转换是开发人员明确需要的。

`int abc = 1.223;`这个语句是会编译告警的。`int abc = static_cast<int>(1.223)`这个语句相当于明确告诉编译器这个转换是我希望的。编译器就不会再告警了。

# dynamic_cast

对于一个对象他可能向上转换,也可以向下转换。向上转换属于允许的隐式转换。所以如果是向上转换,用staic_cast比用dynamic_cast好。dynamic_cast有性能的消耗,他在运行期间要比对继承关系。

如果是向下转换,dynamic_cast有一定的优势,见下面的代码

```

class Base{};

calss Derived:public Base{};

Base b;

Derived d;

Derived* pd1 = static_cast<Derived*>(&d);

Derived*pd2 = dynamic_cast<Derived*>(&d);

```

向下类型转换的时候dynamic_cast会帮你检查当前指针或者引用的运行期类型,如果运行期类型和要转换的类型相同,那么他会进行转换。如果运行期的类型真的需要向下转换,那么他会返回NULL,或者异常。

真正的向下转换是不允许的。但是如果使用static_cast你强制得到的一个指针会掩盖后续有可能出现的野指针问题。

向上转换推荐使用static_cast, 向下转换在设计上要避免,这就使得dynamic_cast很尴尬,基本的建议就是不要使用他。

## 如何从设计上避免使用向下转换

向下转换发生的场景是,你得到一个base类型的指针或者引用,但你明确的知道这个对象实际是个Derived类型,且你需要调用Derived类型下的一个接口,并且遗憾的是这个接口还不是virtual重载的,导致编译器无法帮你通过多态调用。所以你想通过类型转换直接调用。

如何避免,上述已经交代了最好的方法之一,把你要调用的接口在base上加一个virtual函数。

如果别人的base不让你修改,那向下转型你不得不用dynamic_cast,那带来的时间消耗向项目反馈吧。

比如一个容器里面存放Base的指针, 而你需要从这个容器逐个取出指针,判断如果是Derive1类型,执行Derive1的一个函数。如果是Derive2类型,执行Derive2的另一个函数,此时你只能用dynamic_cast 因为只有dynamic_cast才能去判断对象的实际运行期类型是否是你要强制转换的目标类型。 如果不是目标类型,会返回NULL或者异常。

# reinterpret_cast

reinterpret_cast在低级编程或者硬件接口开发中常用,比如我需要把flash的一个地址0x5800FF直接给一个指针变量。这是用用const_cast, stattic_cast dynamic_cast都不合适。只能用reinterpret_cast, 或者退化到c方式的万能转换

```

int *p = reinterpret_cast<int*> (0x5800FF);

```

#

# 当遇到非内置类型的时候要慎用类型转换

这里有几个容易错的类型转换

## 不同的类型转换,得到的值不同

```

class Base{};

calss Derived:public Base{};

Derived d;

Base* pd1 = &d;

Derived* pd2 = &d;

char* pd3 = (char*)&d;

char* pd4 = static_cast<char*>(&d);

```

这里要注意,因为多态的实现,pd1和pd2是可能不一样的。所以对于一个对象,取他的指针的时候,编译器会根据语境进行类型转换,类型转换后的数据是可能不一样。

pd3这样的转换是必须避免的,这种转换语境下,你无法确认最终获取的指针是pd1 还是pd2。pd4的转换是无法进行的,编译器会提示你static_cast不支持这种类型转换。所以使用static_cast相对于传统的(char*)的优势就提现出来了。

## 对象的类型转换会生成临时对象

## 对象类型指针的强制转换不会影响多态的实际调用

这两点是很容易犯错的地方,需要用实际的例子说明

```

class Base{

public:

int a;

int b;

virtual voi init(){a=11; b =12;};

};

class Derive:public Base

{

public:

int c;

void init(){

//实现1:

    Base* pBase = static_cast<Base*>(this);

    pBase->init();

//实现2:

    static_cast<Base*>(this)->init();

//实现3:

    static_cast<Base>(*this).init();

//实现4:

    Base::init();

    c = 13;

}

}

```

实现1-4的代码的意图是在调用Derive类对象的init函数的时候,先执行一下基类的init函数。但是上述实现1和实现2的代码达不到这个效果。虽然你通过指针强制转换得到了pBase的指针,但是调用pBase->init()的时候,编译器检查的是pBase对应对象的运行期类型是Derive类型,调用的还是Derive的init函数,这就会导致程序无限循环导致堆栈溢出。

实现3的代码是生成了一个临时变量,这个临时变量的类型是Base, 然后调用了这个临时变量的init函数,此时虽然调用了Base的init,但已经是在另一个对象上的操作了。

实现4才是正确的实现。

强调的是,多态(具体调用哪个virtual函数)取决于对象运行期的实际类型。引用或者指针的强制类型转换,不会改变运行期的实际类型。

上一篇下一篇

猜你喜欢

热点阅读