我要努力工作,加油!

直接使用(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++的某些设计是不可想象的。