直接使用(type *)的类C语言风格转换指针类型不行吗?C++为什么要引入static_cast、dynamic_cast、const_cast和reinterpret_cast这么麻烦的方法?
发表于: 2019-12-11 20:31:00 | 已被阅读: 87 | 分类于: C++
如果读者和我一样是C语言程序员出身,在使用C++编写程序需要转换变量类型时,一般会直接使用(type)
修饰符(这在C语言程序开发中相当常用),例如将void *
指针转换为int *
指针的代码可以按照下面这样写:
void *ptr = foo();
int *p = (int *)ptr;
之后,便可使用指针 p 按照 int 型数据的方式访问 ptr 指向的内存了。这样的类C语言风格代码在C++程序开发中照样可以很好的工作,但是C++语言却额外引入了几个类型转换操作符:
- static_cast
- dynamic_cast
- const_cast
- reinterpret_cast
这四种操作符的语法格式都是一样的:
xxx_cast<new_type>(data);
例如使用 static_cast 将上述例子中的void *
指针 ptr 转换为int *
指针的C++语言代码可以如下写:
p = static_cast<int *>(ptr);
那么,C++语言中的这几个类型转换操作符相对于类C语言风格的(type)val
转换,有什么特别之处吗?仔细考虑一下,从功能上来看,(type)val
转换方式足够使用了,额外引入这几个操作符有些多余。
所以要考虑这个问题,应该换一个角度,事实上,很多C++程序员认为,C++语言引入这 4 个命名的类型转换操作符,不过是为了增加代码的可视性,不同的操作符用于不同情况下的类型转换,这样可以让代码更加清晰,能够清楚的辨别C++语言代码中的每个显式类型转换的潜在风险级别,例如, 非指针的 static_cast 和 const_cast 要比 reinterpret_cast 更安全。
因此,虽然C++语言仍然支持旧式的类C语言(type)val
类型转换方法,但是既然它新增了这几个操作符,建议只有在C语言或者暂未支持这几个操作符的C++编译器上编写代码时,才使用这种语法(type)val
。
static_cast
static_cast
操作符可以用于不同指针,以及相关类之间的类型转换(向上或者向下),它也可以执行一些隐式转换,例如下面这段C++语言代码:
class Mammal{};
class Human: public Mammal{};
// h 指向派生类对象
Human *h = new Human;
// 转换为基类,此时 static_cast 不是必须的
Mammal *m = static_cast<Mammal *>(h);
// 再转换回派生类
Mammal *m2 = static_cast<Human *>(m);
如代码所述,向上往基类做指针类型转换时,static_cast
并不是必须的,因为每一个Human(人类)
都是Mammal(哺乳动物)
,所以Human *
指针可以被隐式的转换为Mammal *
指针,此时的static_cast
实际上执行的正是这种隐式操作。
所谓“隐式转换”,粗略的来看其实就是编译器帮忙做了自动转换而已。
但若是向下往派生类做指针类型转换时,情况就不同了,Mammal
并不一定都是Human
,所以无法通过隐式转换将Mammal *
指针转换为Human *
指针,因此此时 static_cast 就是必须的了,若是直接执行 m2=m
,将在编译时得到下面这样的错误:
invalid conversion from ‘Mammal’ to ‘Human’
但是需要注意的是,static_cast
虽然能够将Mammal *
指针转换为Human *
指针,但是它不会做任何检查,也就是说,类型转换的有效性由程序员自己负责,对于static_cast
而言,无效的类型转换可能不会在编译时失败,但是却有可能在随后的运行中出错。请看下面这段C++语言代码实例:
class Mammal {};
class Human: public Mammal {
public:
virtual void scream() { // 注意 virtual
std::cout << "MOM" << std::endl;
}
};
Mammal *m = new Mammal;
// 强制将 Mammal *指针转换为 Human * 指针
Human *h = static_cast<Human *>(m);
// 出错
h -> scream();
在上面这段C++语言代码中,我们使用 static_cast 强制将Mammal *
基类指针转换为Human *
派生类指针,这么做也许在编译时不会出错,但是在稍后的运行中将引发段错误。所以,使用 static_cast 做类指针的向下转换时,程序员自己务必要确保转换是有效的。
当然了,static_cast 并不总是能够成功,不相关的类之间就不能成功,例如下面这段C++语言代码示例:
class A {};
class B {};
A *a = new A;
B *b = static_cast<A *>(a);
编译这段代码会得到下面这样的错误:
cannot convert '
A*
' to 'B*
' in initialization
事实上,凡是涉及到内存访问方式改变的转换,static_cast 基本上都不会成功,因为这些转换都是有风险(可参考我之前的文章)的,一般来说,static_cast 不能用于无关类型之间的转换,包括:
- 两个具体类型指针之间的转换,例如
int *
与double *
,class *
与long *
等。 int
和指针之间的转换。
这两种类型的转换都可能以危险的形式访问内存,比如int *
指针本来将其指向地址的接下来 sizeof(int) 字节的内存解释为整数,转换为double *
指针后,则会将其执行地址的接下来 sizeof(double) 字节内存解释为双精度浮点数,这通常不会是期望的结果。而 int 值恰好是合法的无冲突的可读写内存的几率微乎其微,因此 static_cast 不允许这种转换。
static_cast 中的“static”有着“静态”的意思,它在编译时转换,失败时会抛出编译错误。另外需要说明的是,static_cast 不能将 const/volatile 修饰符去掉或者加上,因为这样也是不安全的转换。
dynamic_cast
从字面上来看,dynamic 恰好是 static 的反义词,它的确与static_cast
有不同的地方:一个是编译时转换,一个是运行时转换。
dynamic_cast
用于在类的继承层次之间进行类型转换,它允许向上转换,例如前面例子中的Human *
指针向上转换为Mammal *
指针,也支持向下转换,不过我们已经明白向下转换并不一定有效,幸运的是,dynamic_cast 可以替我们做一些RTTI
(Run-Time Type Identification,运行时类型识别)安全性的检查,请看下面这个C++语言代码示例:
class Mammal{};
class Human: public Mammal{};
Human *h = new Human;
Mammal *m = dynamic_cast<Mammal *>(h); // ok
Human *h1 = dynamic_cast<Human *>(m) // Error
第二个转换将导致错误,除非基类Human
是多态的(拥有至少一个虚函数)。
https://en.wikipedia.org/wiki/Static_cast http://c.biancheng.net/cpp/biancheng/view/3297.html https://stackoverflow.com/questions/103512/why-use-static-castintx-instead-of-intx https://codeburst.io/understanding-c-casts-ef1f36e54240
以C语言程序员的眼光来看,C++的某些设计是不可想象的。