欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

C++中static_cast/const_cast/dynamic_cast/reinterpret_cast的区别和使用

程序员文章站 2022-09-28 13:18:40
C风格的强制转换较简单,如将float a转换为int b,则可以这样:b = (int)a,或者b=int(a)。 C++类型转换分为隐式类型转换和显示类型转换。 隐式类型转...

C风格的强制转换较简单,如将float a转换为int b,则可以这样:b = (int)a,或者b=int(a)。

C++类型转换分为隐式类型转换和显示类型转换。

隐式类型转换又称为标准转换,包括以下几种情况:

(1)、算术转换:在混合类型的算术表达式中,最宽的数据类型成为目标转换类型;

(2)、一种类型表达式赋值给另一种类型的对象:目标类型是被赋值对象的类型;

(3)、将一个表达式作为实参传递给函数调用,此时形参和实参类型不一致:目标转换类型为形参的类型;

(4)、从一个函数返回一个表达式,表达式类型与返回类型不一致:目标转换类型为函数的返回类型。

显示类型转换被称为强制类型转换(cast)。

C++提供了四种强制类型转换形式:static_cast、const_cast、dynamic_cast、reinterpret_cast.每一种适用于特定的场合。

static_cast语法:static_cast(expression)

static_cast:仅根据表达式中存在的类型,将expression转换为type-id类型。此运算符可用于将指向基类的指针转换为指向派生类的指针等操作。此类转换并非始终安全。

通常使用 static_cast 转换数值数据类型,例如将枚举型转换为整型或将整型转换为浮点型,而且你能确定参与转换的数据类型。 static_cast转换安全性不如dynamic_cast转换,因static_cast不执行运行时类型检查,而dynamic_cas执行该检查。对不明确的指针的 dynamic_cast将失败,而static_cast的返回结果看似没有问题,这是危险的。尽管 dynamic_cast转换更加安全,但是dynamic_cast只适用于指针或引用,而且运行时类型检查也是一项开销。dynamic_cast 和static_cast运算符可以在整个类层次结构中移动指针。然而,static_cast完全依赖于转换语句提供的信息,因此可能不安全。

static_cast可以反向执行隐式转换,可用于任何隐式允许的转换类型,而在这种情况下结果是不确定的。这需要程序员来验证static_cast转换的结果是否安全。

static_cast可用于将int转换为char。但是,得到的char可能没有足够的位来保存整个int值。同样,这需要程序员来验证static_cast转换的结果是否安全。

static_cast运算符还可用于执行任何隐式转换,包括标准转换和用户定义的转换。

static_cast 运算符可以将整数值显式转换为枚举类型。如果整型值不在枚举值的范围内,生成的枚举值是不确定的。

static_cast运算符将null指针值转换为目标类型的null指针值。

任何表达式都可以通过static_cast运算符显式转换为void类型。目标void类型可以选择性地包含const、volatile或__unaligned特性。

static_cast运算符无法转换掉const、volatile或 __unaligned特性。

只有在确信代码将正常运行的时候,在性能关键代码中使用 static_cast。如果必须在发布模式下使用static_cast,请在调试版本中用 safe_cast(C++ 组件扩展)替换它以确保成功。

const_cast语法:const_cast(expression)

const_cast:从类中移除const、volatile和__unaligned特性。

指向任何对象类型的指针或指向数据成员的指针可显式转换为完全相同的类型(const、volatile 和 __unaligned 限定符除外)。对于指针和引用,结果将引用原始对象。对于指向数据成员的指针,结果将引用与指向数据成员的原始(未强制转换)的指针相同的成员。根据引用对象的类型,通过生成的指针、引用或指向数据成员的指针的写入操作可能产生未定义的行为。

不能使用const_cast运算符直接重写常量变量的常量状态。

const_cast运算符将null指针值转换为目标类型的null指针值。

dynamic_cast语法:dynamic_cast(expression)

dynamic_cast:type-id必须是一个指针或引用到以前已定义的类类型的引用或“指向 void的指针”。如果type-id是指针,则expression的类型必须是指针,如果type-id是引用,则为左值。

在托管代码中的 dynamic_cast的行为中有两个重大更改:(1)、为指针的dynamic_cast对指向装箱的枚举的基础类型的指针将在运行时失败,则返回0而不是已转换的指针。(2)、dynamic_cast 将不再引发一个异常,当type-id是指向值类型的内部指针,则转换在运行时失败。该转换将返回0指示运行值而不是引发。

如果type-id是指向expression的明确的可访问的直接或间接基类的指针,则结果是指向type-id类型的唯一子对象的指针。

如果type-id为void*,则做运行时进行检查确定expression的实际类型。结果是指向byexpression的完整的对象的指针。

如果type-id不是 void*,则做运行时进行检查以确定是否由expression指向的对象可以转换为由type-id指向的类型。如果expression类型是type-id类型的基类,则做运行时检查来看是否expression确实指向type-id类型的完整对象。如果为true,则结果是指向type-id类型的完整对象的指针。

dynamic_cast运算符还可以使用执行“相互转换”。使用同一个类层次结构可能进行指针转换。

当使用dynamic_cast时,如果expression无法安全地转换成类型type-id,则运行时检查会引起变换失败。

指针类型的非限定转换的值是null指针。引用类型的非限定转换会引发bad_cast异常。

reinterpret_cast语法:reinterpret_cast(expression)

reinterpret_cast:允许将任何指针转换为任何其他指针类型。也允许将任何整数类型转换为任何指针类型以及反向转换。

滥用reinterpret_cast运算符可能很容易带来风险。除非所需转换本身是低级别的,否则应使用其他强制转换运算符之一。

reinterpret_cast运算符可用于char*到int*或One_class*到Unrelated_class*之类的转换,这本身并不安全。

reinterpret_cast的结果不能安全地用于除强制转换回其原始类型以外的任何用途。在最好的情况下,其他用途也是不可移植的。

reinterpret_cast运算符不能丢掉const、volatile或__unaligned特性。

reinterpret_cast运算符将null指针值转换为目标类型的null指针值。

reinterpret_cast的一个实际用途是在哈希函数中,即,通过让两个不同的值几乎不以相同的索引结尾的方式将值映射到索引。

static_cast is the first cast you should attempt to use. It does things like implicit conversions between types (such as int to float, or pointer to void*), and it can also call explicit conversion functions (or implicit ones). In many cases,explicitly stating static_cast isn't necessary, but it's important to note that the T(something) syntax is equivalent to (T)something and should be avoided(more on that later). A T(something, something_else) is safe, however, and guaranteed to call the constructor.

static_cast can also cast through inheritance hierarchies. It is unnecessary when casting upwards (towards a base class), but when casting downwards it can be used as long as it doesn't cast through virtual inheritance. It does not do checking,however, and it is undefined behavior to static_cast down a hierarchy to a type that isn't actually the type of the object.

const_cast can be used to remove or add const to a variable; no other C++ cast is capable of removing it (not even reinterpret_cast). It is important to note that modifying a formerly const value is only undefined if the original variable is const; if you use it to take the const off a reference to something that wasn't declared with const, it is safe. This can be useful when overloading member functions based on const, for instance. It can also be used to add const to an object,such as to call a member function overload.

const_cast also works similarly on volatile, though that's less common.

dynamic_cast is almost exclusively used for handling polymorphism. You can cast a pointer or reference to any polymorphic type to any other class type (a polymorphic type has at least one virtual function, declared or inherited). You can use it for more than just casting downwards -- you can cast sideways or even up another chain. The dynamic_cast will seek out the desired object and return it if possible. If it can't, it will return nullptr in the case of a pointer, or throw std::bad_cast in the case of a reference.

dynamic_cast has some limitations, though. It doesn't work if there are multiple objects of the same type in the inheritance hierarchy (the so-called 'dreaded diamond') and you aren't using virtual inheritance. It also can only go through public inheritance - it will always fail to travel through protected or private inheritance. This is rarely an issue, however, as such forms of inheritance are rare.

reinterpret_cast is the most dangerous cast, and should be used very sparingly. It turns one type directly into another - such as casting the value from one pointer to another, or storing a pointer in an int, or all sorts of other nasty things.Largely, the only guarantee you get with reinterpret_cast is that normally if you cast the result back to the original type, you will get the exact same value (but not if the intermediate type is smaller than the original type).There are a number of conversions that reinterpret_cast cannot do, too. It's used primarily for particularly weird conversions and bit manipulations, like turning a raw data stream into actual data, or storing data in the low bits of an aligned pointer.

C casts are casts using (type)object or type(object). A C-style cast is defined as the first of the following which succeeds:(1)、const_cast; (2)、static_cast(though ignoring access restrictions); (3)、static_cast (see above), then const_cast; (4)、reinterpret_cast; (5)、reinterpret_cast, then const_cast。

It can therefore be used as a replacement for other casts in some instances, but can be extremely dangerous because of the ability to devolve into a reinterpret_cast,and the latter should be preferred when explicit casting is needed, unless you are sure static_cast will succeed or reinterpret_cast will fail. Even then,consider the longer, more explicit option.

C-style casts also ignore access control when performing a static_cast, which means that they have the ability to perform an operation that no other cast can. This is mostly a kludge, though, and in my mind is just another reason to avoid C-style casts.

测试代码如下:

static_cast.hpp:

 

#ifndef FBC_MESSY_TEST_STATIC_CAST_HPP_
#define FBC_MESSY_TEST_STATIC_CAST_HPP_

#include 

// reference: https://msdn.microsoft.com/zh-cn/library/c36yw7x9.aspx
class B1 {
public:
	virtual void Test(){}
};

class D1 : public B1 {};

class CCTest {
public:
	void setNumber(int);
	void printNumber() const;
private:
	int number;
};

class B2 { };
class C2 : public B2 { };
class D2 : public C2 { };

class A3 { virtual void f(); };
class B3 { virtual void f(); };

class B4 { virtual void f(); };
class D4 : public B4 { virtual void f(); };

// Returns a hash code based on an address
unsigned short Hash(void *p);

void test_static_cast1();
void test_static_cast2(B1* pb, D1* pd);
void test_static_cast3(B1* pb);
void test_static_cast4();
void test_static_cast5();
void test_static_cast6();
void test_static_cast7();
void test_static_cast8();
void test_static_cast9();
void test_static_cast10();

#endif // FBC_MESSY_TEST_STATIC_CAST_HPP_
static_cast.cpp:

 

 

#include "static_cast.hpp"
#include 

void CCTest::setNumber(int num) { number = num; }

void CCTest::printNumber() const {
	std::cout << "\nBefore: " << number;
	//this 指针的数据类型为 const CCTest *。
	//const_cast 运算符会将 this 指针的数据类型更改为 CCTest *,以允许修改成员 number。
	//强制转换仅对其所在的语句中的其余部分持续
	const_cast< CCTest * >(this)->number--;
	std::cout << "\nAfter: " << number;
}

void A3::f()
{

}

void B3::f()
{

}

void B4::f()
{

}

void D4::f()
{

}

unsigned short Hash(void *p) {
	//reinterpret_cast 允许将指针视为整数类型。结果随后将按位移位并与自身进行“异或”运算以生成唯一的索引(具有唯一性的概率非常高)。
	//该索引随后被标准 C 样式强制转换截断为函数的返回类型。
	unsigned int val = reinterpret_cast(p);
	return (unsigned short)(val ^ (val >> 16));
}

// C风格强制类型转换
void test_static_cast1()
{
	float a = 1.1, b = 1.9;

	int ret1 = (int)a;
	int ret2 = int(b);

	std::cout << ret1 << "    " << ret2 << "    " << std::endl;
}

void test_static_cast2(B1* pb, D1* pd)
{
	//与 dynamic_cast 不同,pb 的 static_cast 转换不执行运行时检查。
	//由 pb 指向的对象可能不是 D 类型的对象,在这种情况下使用 *pd2 会是灾难性的。
	//例如,调用 D 类(而非 B 类)的成员函数可能会导致访问冲突。
	D1* pd2 = static_cast(pb);   // Not safe, D can have fields and methods that are not in B.
	B1* pb2 = static_cast(pd);   // Safe conversion, D always contains all of B.
}

void test_static_cast3(B1* pb)
{
	//如果 pb 确实指向 D 类型的对象,则 pd1 和 pd2 将获取相同的值。如果 pb == 0,它们也将获取相同的值。
	//如果 pb 指向 B 类型的对象,而非指向完整的 D 类,则 dynamic_cast 足以判断返回零。
	//但是,static_cast 依赖于程序员的断言,即 pb 指向 D 类型的对象,因而只是返回指向那个假定的 D 对象的指针。
	D1* pd1 = dynamic_cast(pb);
	D1* pd2 = static_cast(pb);
}

void test_static_cast4()
{
	char ch;
	int i = 65;
	float f = 2.5;
	double dbl;

	ch = static_cast(i);   // int to char
	dbl = static_cast(f);   // float to double
	i = static_cast(ch);
}

void test_static_cast5()
{
	CCTest X;
	X.setNumber(8);
	X.printNumber();
}

void test_static_cast6(D2* pd)
{
	//此转换类型称为“向上转换”,因为它将在类层次结构上的指针,从派生的类移到该类派生的类。向上转换是一种隐式转换。
	C2* pc = dynamic_cast(pd);   // ok: C is a direct base class pc points to C subobject of pd 
	B2* pb = dynamic_cast(pd);   // ok: B is an indirect base class pb points to B subobject of pd
}

void test_static_cast7()
{
	A3* pa = new A3;
	B3* pb = new B3;
	void* pv = dynamic_cast(pa);// pv now points to an object of type A
	pv = dynamic_cast(pb);// pv now points to an object of type B
}

void test_static_cast8()
{
	B4* pb = new D4;   // unclear but ok
	B4* pb2 = new B4;

	//此转换类型称为“向下转换”,因为它将在类层次结构下的指针,从给定的类移到该类派生的类。
	D4* pd = dynamic_cast(pb);   // ok: pb actually points to a D
	D4* pd2 = dynamic_cast(pb2);   // pb2 points to a B not a D
}

void test_static_cast9()
{
	A3* pa = new A3;
	B3* pb = dynamic_cast(pa);   // fails at runtime, not safe;B not derived from A
}

void test_static_cast10()
{
	int a[20];
	for (int i = 0; i < 20; i++) {
		std::cout << Hash(a + i) << std::endl;
	}
}