前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >C++的四种强制转换

C++的四种强制转换

作者头像
方亮
发布于 2019-01-16 06:28:34
发布于 2019-01-16 06:28:34
2.4K00
代码可运行
举报
文章被收录于专栏:方亮方亮
运行总次数:0
代码可运行

C++的四种强制转换

        C++中的四种转换,是一个老生常谈的话题。但是对于初学者来说,该如何选择哪种转换方式仍然会有点困惑。而且我总是觉得“纸上得来终觉浅”,于是便“绝知此事要躬行”。于是利用闲暇时光,整理一下reinterpret_cast、const_cast、static_cast和dynamic_cast这四种强制转换的相关知识。(转载请指明出于breaksoftware的csdn博客)

        一般来说,我们需要类型转换的场景可以分为如下几种:

  • 整型和浮点型相互转换。这种转换往往是在数学计算的场景下。比如一个库函数导出的是double型数据,而我们使用该数据的函数的参数要求是整型,于是我们就需要对其进行转换。反之亦然。
  • 整型和指针相互转换。当我们试图根据某个成员变量的偏移位计算其在该对象内存空间位置时,就会需要将指针转换为整型进行计算。当计算出该变量的位置后(整型),就需要将其转换为指针类型。
  • 整型和枚举类型相互转换。这种转换往往发生在数学计算的场景下。因为枚举一般只是用于表意,而实际参与运算的还是整型数据。
  • 指针和无类型指针相互转换。一个典型的场景是,win32编程中,线程函数的入参要求是个LPVOID型数据。而我们往往将类对象的指针传递进去,以方便我们调用封装在类中的相关函数和变量。即CreateThread时将指针转为void*型,在线程函数中将void*转为指针。
  • 无关系类指针的相互转换。这种场景并不多见。
  • 存在继承关系的类指针相互转换。多发生在多态等场景下。
  • 转换不同长度的数据。这是个转换截断的问题,在现实使用中,也不难见到。

        在测试如上场景时,我们往往会遇到阻碍。这种阻碍来源于两个方面:

  • 编译器出错。这是因为语法规定这种使用不合法。所以编译器在编译代码时,认为该行为违法,终止之后的流程。
  • 运行时出错。这是因为在语法上是合法的,但是运行时是不合理的

        为了更好讨论如上场景,我们先预备一些辅助结构。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class Parent {
public:
	Parent() {m_strPointerFrom = "Parent Pointer";};
public:
	void print() {printf("%s From Parent Func\n", m_strPointerFrom.c_str());};
	virtual void printv() {printf("%s From Parent Virutal Func\n", m_strPointerFrom.c_str());};
protected:
	std::string m_strPointerFrom;
};

//#define USEERROR

class Child : public Parent {

#ifndef USEERROR

public:
	Child() {m_strPointerFrom = "Child Pointer";};
public:
	void print() {printf("%s From Child Func.\n", m_strPointerFrom.c_str());};
	virtual void printv() override {printf("%s From Child Virutal Func.\n", m_strPointerFrom.c_str());};
#else

public:
	Child() {m_strPointerFrom = "Child Pointer"; m_strOnlyChild = "OnlyInChild";};
public:
	void print() {printf("%s From Child Func. %s\n", m_strPointerFrom.c_str(), m_strOnlyChild.c_str());};
	virtual void printv() override {printf("%s From Child Virutal Func. %s\n", m_strPointerFrom.c_str(), m_strOnlyChild.c_str());};
private:
	std::string m_strOnlyChild;

#endif

};

class Temp{};

        Temp类是一个无继承关系的原始类。

        Child类继承于Parent类。

        Child类中print函数隐藏了Parent类中定义的print函数的实现。

        Child类也实现了Parent类中的虚方法printv。

        为了区分Parent和Child类的对象,我们设计了一个变量——m_strPointerFrom。这两个类的所有方法都将这个变量打印出来,以帮助我们在之后的测试中标识其来源。

        我们使用预定义控制了Child类的实现。我们可以先关注没有定义USEERROR这个宏的版本。另外一个版本我们将在后面介绍其设计意图。

        如果是编译期间出错,我将在给出的代码示例中,使用注释方法使该代码行失效。如果是运行期间出错,我们将任由之存在,但是会在最后点出其出错的地方。所以看本博文切不可“断章取义”。在引入C++四种转换之前,我们先看下最常见的一种转换——类C语言方式的转换。

类C类型的强制转换

        类c类型的强制转换是我们最常见的一种转换,比如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int a = 0;
double b = (double)a;

        我们列出这种方式,是为了让其和我们即将讨论的四种C++强制转换进行对比。根据之前设计的方案,我们列出如下代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void c_like_cast_test() {

	{
		Parent* pParent = new (std::nothrow) Parent();
		if (!pParent) {
			return;
		}

		int a = (int)(pParent);			// 指针向整型转换
		Parent* pParent1 = (Parent*)(a);// 整型向指针转换

		double b = (double)(a);			// 整型向浮点型转换
		int a1 = (int)b;				// 浮点型向整型转换

		void* pv = (void*)pParent;		// 指针向无类型指针转换
		Parent* pParent2 = (Parent*)pv;	// 无类型指针向指针转换
		Temp* pTemp = (Temp*)pParent;	// 无关系类型指针相互转换
		Parent* pParent3 = (Parent*)pTemp;// 无关系类型指针相互转换
		ETYPE e = (ETYPE)a;				// 整型向枚举转换
		int a2 = (int)e;				// 枚举向整型转换
		int a3 = reinterpret_cast<int>(pv);					// 无类型指针转整型
		Temp* pTemp1 = reinterpret_cast<Temp*>(pv);			// 无类型指针转其他指针
		delete pParent;}

        可以见得类C的转换对如上四种相互转换并不存在编译问题。至于是否存在运行时问题,就要看我们对数据的预期和对相关指针的使用了。

        我们再看存在父子关系的指针的转换及不同长度类型数据转换的例子:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
	{       // A
		Parent* pParent = new (std::nothrow) Parent();
		if (!pParent) {
			return;
		}
		pParent->print();
		pParent->printv();
		Child* pChild = (Child*)(pParent);
		pChild->print();				
		pChild->printv();
		delete pParent;
	}

	{       //B
		Child* pChild = new (std::nothrow) Child();
		if (!pChild) {
			return;
		}
		pChild->print();
		pChild->printv();

		Parent* pParent = (Parent*)(pChild);
		pParent->print();				
		pParent->printv();
		delete pChild;
	}

	{
		Child* pChild = new (std::nothrow) Child();
		if (!pChild) {
			return;
		}
		Temp* pTemp = (Temp*)pChild;
		delete pChild;
	}

	{
		intptr_t p = 0xF000000000000001;
		Parent* pP = (Parent*)(p);
		printf("0x%x\n", pP);
	}
}

        从这段代码可以看出,我们没有将任何一行有效代码注释掉。这个说明如上的写法也不会导致编译期间出现问题——但是这并不意味着这样的代码就是正确的——父子指针转换可能会导致运行期出错。这个问题我们会在之后讨论。我们先看下执行的结果。

        上图中A、B区域和代码中的A、B区域相对照。由上面可以得出如下结论:

  • 调用的被隐藏函数实体(函数入口)是由其调用时的指针类型决定的——print函数是调用Child版(From Child Func)的还是Parent版(From Parent Func)?这是由其左侧指针类型决定的。如果左侧指针是Child指针类型,则调用的是Child的print。如果左侧指针是Parent指针类型,则调用的Parent的print。
  • 调用的虚函数实体(函数入口)是由对象的实际类型决定的——printv函数是调用Child版(From Child Virtual Func)还是Parent版(From Parent Virtual Func)?这是由调用者最初被new出来时的类型决定的。如果是调用new (std::nothrow) Child,则调用的是Child的printv。如果是调用new (std::nothrow) Parent,则调用的是Parent的printv。
  •  无论指针在被创建后如何转换,其指向的依旧是初始时new出来的对象——可以见得A区域中的指针都指向了Parent类对象(Parent Pointer),而B区域中的指针都指向了Child类的对象(Child Pointer)。

        说到这个问题,可能就要扯一点C++对象的内存模型。这儿我并不详细介绍其模型,只是想引出几个原理:

  • 类成员函数的实现,在内存中是有一个唯一入口和唯一代码片的。可以想象下,这段代码片和类数据是“分离”的,它们只是在编译期间由编译器保证其相关性。
  • 驱动类函数执行的是类的this指针所指向的数据区。其实类的非静态函数的第一个参数——也是隐藏的参数是这个类的this指针。通过该this指针,该函数才能访问到对象的成员数据。于是在多线程环境下,一个对象的函数在被多个线程执行时,它们会可能会修改同一个this指针的同一个数据。

        如果能正确理解如上两点,则上例中的结果便可以得到理解了。

        再回到类型转换上来。可以说类C的强制转换的能力是非常强大的,使用这种方法就意味着“通吃”。这也是大家非常喜欢使用它的一个原因。为什么它这么强大,我们看下汇编,以其中几段为例:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
		Parent* pParent = (Parent*)(pChild);
01077A5D  mov         eax,dword ptr [pChild]  
01077A60  mov         dword ptr [pParent],eax  
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
		int a = (int)(pParent);		// 指针向int型转换
01077896  mov         eax,dword ptr [pParent]  
01077899  mov         dword ptr [a],eax  

        由上可见,它只是简单的二进制拷贝的过程。所以这种简单的实现,使之有强大的功能。但是也是这种简单的设计,使之使用也存在隐患,这个我们会在之后讨论。

reinterpret_cast

        reinterpret_cast是四种C++强制转换中和类C强制转换最接近的了。我们看一段来自cplusplus网站上对该转换的说明:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/*
reinterpret_cast converts any pointer type to any other pointer type, even of unrelated classes.
The operation result is a simple binary copy of the value from one pointer to the other.
All pointer conversions are allowed: neither the content pointed nor the pointer type itself is checked.
It can also cast pointers to or from integer types. 
The format in which this integer value represents a pointer is platform-specific. 
The only guarantee is that a pointer cast to an integer type large enough to fully contain it (such as intptr_t),
is guaranteed to be able to be cast back to a valid pointer.
reinterpret可用于任何指针向任何指针的转换。它只是简单的进行二进制拷贝。
它还可以用于将指针类型和整型类型相互转换(注意整型类型和指针类型的长度不一致)。
它不进行类型检查。
*/

        从这段说明来看,其和类C转换没什么区别。但是我们的实验代码将证明它们还是存在不同之处的。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void reinterpret_cast_test() {
	{
		Parent* pParent = new (std::nothrow) Parent();
		if (!pParent) {
			return;
		}

		int a = reinterpret_cast<int>(pParent);				// 指针向整型转换
		Parent* pParent1 = reinterpret_cast<Parent*>(a);	// 整型向指针转换

//		double b = reinterpret_cast<double>(a);				// 整型向浮点型转换
//		int a1 = reinterpret_cast<int>(b);					// 浮点型向整型转换

		void* pv = reinterpret_cast<void*>(pParent);		// 指针向无类型指针转换
		Parent* pParent2 = reinterpret_cast<Parent*>(pv);	// 无类型指针向指针转换

 		Temp* pTemp = reinterpret_cast<Temp*>(pParent);		// 无关系类型指针相互转换
 		Parent* pParent3 = reinterpret_cast<Parent*>(pTemp);// 无关系类型指针相互转换

// 		ETYPE e = reinterpret_cast<ETYPE>(a);				// 整型向枚举转换
// 		int a2 = reinterpret_cast<int>(e);					// 枚举向整型转换

		int a1 = reinterpret_cast<int>(pv);					// 无类型指针转整型
		Temp* pTemp1 = reinterpret_cast<Temp*>(pv);			// 无类型指针转其他指针

		delete pParent;
	}

        上述代码中,我们一共注释了4行。这四行是会在编译时出错的。所以我们可以见得reinterpret_cast不可用于浮点和整型之间的转换。也不可以用于枚举和整型的转换。但是可以发现,它还是很强大的的——因为它也是简单的二进制转换:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 		Temp* pTemp = reinterpret_cast<Temp*>(pParent);		// 无关系类型指针相互转换
013021DE  mov         eax,dword ptr [pParent]  
013021E1  mov         dword ptr [pTemp],eax  
 		Parent* pParent3 = reinterpret_cast<Parent*>(pTemp);// 无关系类型指针相互转换
013021E4  mov         eax,dword ptr [pTemp]  
013021E7  mov         dword ptr [pParent3],eax  

        其它父子类指针的代码及长度不一致的转换就不贴出代码了,这些代码和类C强制转换差不多,只是将转换方式改了下。

        由上我们可以总结出:reinterpret_cast转换是在类C转换的基础上,在编译期间

  • 约束了整型、浮点型和枚举类型的相互转换。

        那么C++中有没有提供整型、浮点和枚举类型的相互转换方法呢?有的!见static_cast。

static_cast

        static_cast也是使用非常多的一种强制转换。我们看一段来自cplusplus网站上对该转换的说明:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/*
static_cast can perform conversions between pointers to related classes, 
not only upcasts (from pointer-to-derived to pointer-to-base),
but also downcasts (from pointer-to-base to pointer-to-derived). 
No checks are performed during runtime to guarantee that the object being converted is in fact a full object of the destination type. 
Therefore, it is up to the programmer to ensure that the conversion is safe. 
On the other side, it does not incur the overhead of the type-safety checks of dynamic_cast.
它用于在存在继承关系的类指针之间转换。可以从派生类指针转为基类指针,也可以从基类指针转为派生类指针。

static_cast is also able to perform all conversions allowed implicitly
(not only those with pointers to classes), and is also able to perform the opposite of these. 
It can:
Convert from void* to any pointer type. In this case, 
it guarantees that if the void* value was obtained by converting from that same pointer type, the resulting pointer value is the same.
Convert integers, floating-point values and enum types to enum types.
它可以将void*型向任意指针类型转换。还可以在整型、浮点型和枚举型将相互转换。
*/

        看了这个说明,似乎static_cast可以实现类C转换的所有场景了。但是实际不然

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void static_cast_test() {

	{
		Parent* pParent = new (std::nothrow) Parent();
		if (!pParent) {
			return;
		}

//		int a = static_cast<int>(pParent);				// 指针向整型转换
//		Parent* pParent1 = static_cast<Parent*>(a);		// 整型向指针转换

		int a = 1;
		double b = static_cast<double>(a);				// 整型向浮点型转换
		int a1 = static_cast<int>(b);					// 浮点型向整型转换

		void* pv = static_cast<void*>(pParent);			// 指针向无类型指针转换
		Parent* pParent2 = static_cast<Parent*>(pv);	// 无类型指针向指针转换

// 		Temp* pTemp = static_cast<Temp*>(pParent);		// 无关系类型指针相互转换
// 		Parent* pParent3 = static_cast<Parent*>(pTemp);	// 无关系类型指针相互转换
		
		ETYPE e = static_cast<ETYPE>(a);				// 整型向枚举转换
		int a2 = static_cast<int>(e);					// 枚举向整型转换

//		int a3 = static_cast<int>(pv);					// 无类型指针转整型
		Temp* pTemp = static_cast<Temp*>(pv);			// 无类型指针转其他指针

		delete pParent;
	}

        可以见得static_cast

  • 约束了指针和整型的相互转换。
  • 约束了无关系类型的指针的相互转换。(无类型指针除外)

        其他继承关系类指针相互转换也不列出了。其代码同类C相似,只是修改了操作方式。而且static_cast在汇编级的代码和类C强制转换是一致的。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
		double b = (double)(a);			// 整型向浮点型转换
01291872  fild        dword ptr [a]  
01291875  fstp        qword ptr [b]  
		int a1 = (int)b;				// 浮点型向整型转换
01291878  fld         qword ptr [b]  
0129187B  call        @ILT+420(__ftol2_sse) (12911A9h)  
01291880  mov         dword ptr [a1],eax  

		void* pv = (void*)pParent;		// 指针向无符号指针转换
01291883  mov         eax,dword ptr [pParent]  
01291886  mov         dword ptr [pv],eax  
		Parent* pParent2 = (Parent*)pv;	// 无符号指针向指针转换
01291889  mov         eax,dword ptr [pv]  
0129188C  mov         dword ptr [pParent2],eax

        上面代码是类C的强制转换,下面的是static_cast的。我将整型和浮点相互转换的反汇编代码也提了出来,可以见得也是一样的。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
		double b = static_cast<double>(a);				// 整型向浮点型转换
012928CD  fild        dword ptr [a]  
012928D0  fstp        qword ptr [b]  
		int a1 = static_cast<int>(b);					// 浮点型向整型转换
012928D3  fld         qword ptr [b]  
012928D6  call        @ILT+420(__ftol2_sse) (12911A9h)  
012928DB  mov         dword ptr [a1],eax  

		void* pv = static_cast<void*>(pParent);			// 指针向无符号指针转换
012928DE  mov         eax,dword ptr [pParent]  
012928E1  mov         dword ptr [pv],eax  
		Parent* pParent2 = static_cast<Parent*>(pv);	// 无符号指针向指针转换
012928E4  mov         eax,dword ptr [pv]  
012928E7  mov         dword ptr [pParent2],eax  

        说完上述两种转换,我们可以发现:在reinterpret_cast上,没有任何面向对象的痕迹。而static_cast出现了对继承的关系的约束。之后我们将介绍C++特性更强的转换——dynamic_cast。

dynamic_cast

        在讨论dynamic_cast之前,我们要先回到最前面定义的两个辅助类——Parent和Child上。之前为了保证这两个类指针在相互转换后,调用相关函数不会出现运行时错误,我们没有定义USEERROR宏。现在我们要开启USERROR宏,使得Child类比Parent类多一个成员变量——m_strOnlyChild。并在Child类重写函数print和继承的虚函数printv中使用到该变量。于是我们之前的类C强制转换、reinterpret_cast和static_cast对父子类指针转换后函数调用,将出现运行时出错。我们以static_cast为例:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
	{
		Parent* pParent = new (std::nothrow) Parent();
		if (!pParent) {
			return;
		}
		pParent->print();
		pParent->printv();
		Child* pChild = static_cast<Child*>(pParent);
		pChild->print();							
		pChild->printv();
		delete pParent;
	}

        代码运行到pChild->print()时将出错。根据我们之前介绍的C++对象模型的知识,我们可以知道pChild指针仍然指向的是一个Parent对象。因为print是被隐藏函数,其左侧指针pChild是Child*型,所以编译器会将调用指向Child的print函数入口。而Child的print函数需要的成员变量m_strOnlyChild只在Child对象中存在,而不在Parent对象内存空间中。所以运行时会报非法地址访问之类的错误。

        在知道之前我们父类对象向子类指针转换的过程存在如此不安全的行为时,我们就要介绍dynamic_cast了。它有着很强烈的C++特性。我们先看下说明:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/*
dynamic_cast can only be used with pointers and references to classes (or with void*). 
Its purpose is to ensure that the result of the type conversion points to a valid complete object of the destination pointer type.
This naturally includes pointer upcast (converting from pointer-to-derived to pointer-to-base), in the same way as allowed as an implicit conversion.
But dynamic_cast can also downcast (convert from pointer-to-base to pointer-to-derived) polymorphic classes (those with virtual members)
if -and only if- the pointed object is a valid complete object of the target type.

dynamic_cast can also perform the other implicit casts allowed on pointers: casting null pointers between pointers types (even between unrelated classes),
and casting any pointer of any type to a void* pointer.
dynamic_cast只可以用于指针之间的转换,它还可以将任何类型指针转为无类型指针,甚至可以在两个无关系的类指针之间转换。
*/

        由上可以知道,dynamic_cast在编译层约束了非指针类型的转换。于是我们的测试代码中很多被注释掉

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class TmpBase{ public: virtual void fun(){};};
class TmpDerived : public TmpBase{public: virtual void fun() override {};};

void dynamic_cast_test() {
	{
		Parent* pParent = new (std::nothrow) Parent();
		if (!pParent) {
			return;
		}

//		int a = dynamic_cast<int>(pParent);				// 指针向整型转换
//		Parent* pParent1 = dynamic_cast<Parent*>(a);	// 整型向指针转换

//		double b = dynamic_cast<double>(a);				// 整型向浮点型转换
//		int a1 = dynamic_cast<int>(b);					// 浮点型向整型转换

		void* pv = dynamic_cast<void*>(pParent);		// 指针向无符号指针转换
//		Parent* pParent2 = dynamic_cast<Parent*>(pv);	// 无符号指针向指针转换

		Temp* pTemp = dynamic_cast<Temp*>(pParent);		// 无关系类型指针相互转换
//		Parent* pParent3 = dynamic_cast<Parent*>(pTemp);// 无关系类型指针相互转换

// 		ETYPE e = dynamic_cast<ETYPE>(a);				// 整型向枚举转换
// 		int a2 = dynamic_cast<int>(e);					// 枚举向整型转换

// 		int a2 = dynamic_cast<int>(pv);					// 无符号指针转整型
// 		Temp* pTemp1 = dynamic_cast<Temp*>(pv);			// 无符号指针转其他指针

		TmpDerived* pTmp2 = dynamic_cast<TmpDerived*>(pParent);
		Child* pChild1 = dynamic_cast<Child*>(pTmp2);

		delete pParent;
	}

        这段代码最有意思的是后面的两个转换

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
		TmpDerived* pTmp2 = dynamic_cast<TmpDerived*>(pParent);
		Child* pChild1 = dynamic_cast<Child*>(pTmp2);

        dynamic_cast将两个无关系的类指针进行了转换,而且没有出现编译错误。我们看到TmpDerived类继承于TmpBase类,且TmpBase类中存在虚函数。这样设计的原因,是为了保证dynamic_cast操作的指针是具有多态特性。否则编译器会报错。我们看下其汇编便可知

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
		TmpDerived* pTmp2 = dynamic_cast<TmpDerived*>(pParent);
00D52D16  push        0  
00D52D18  push        offset TmpDerived `RTTI Type Descriptor' (0D5E034h)  
00D52D1D  push        offset Parent `RTTI Type Descriptor' (0D5E000h)  
00D52D22  push        0  
00D52D24  mov         eax,dword ptr [pParent]  
00D52D27  push        eax  
00D52D28  call        @ILT+885(___RTDynamicCast) (0D5137Ah)  
00D52D2D  add         esp,14h  
00D52D30  mov         dword ptr [pTmp2],eax 

        dynamic_cast在底层并不像之前的介绍的几种转换方法使用简单的内存拷贝,而是使用了RTTI技术,所以它要求操作的指针是多态的。因为上例中两个类不存在继承关系,所以每个转换操作都是失败的——返回Null。这样的特性就要求我们在使用dynamic_cast时,需要对返回结果判空,否则就会出现空指针问题。而带来的好处是,我们将避免之前遇到的运行时出错的场景——这个场景排查起来相对困难些。

        我们看段使用dynamic_cast规避掉运行时出错的代码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
	{
		Parent* pParent = new (std::nothrow) Parent();
		if (!pParent) {
			return;
		}
		pParent->print();
		pParent->printv();
		Child* pChild = dynamic_cast<Child*>(pParent);
		if (pChild) {										// Bad
			pChild->print();							
			pChild->printv();
		}
		else {
			printf("dynamic_cast error\n");	// Hit
		}

		try {
			Child& c = dynamic_cast<Child&>(*pParent);		// Bad
		}
		catch (std::bad_cast& e) {
			printf("dynamic_cast error : %s\n", e.what());	// Hit
		} 

		delete pParent;
	}

        根据之前介绍的知识,可以得知我们创建的是Parent对象。因为将Parent对象转换为Child指针存在潜在的安全问题。dynamic_cast将会对这次操作返回Null。以保证我们代码的运行安全性。这儿有个需要指出的是,如果我们使用dynamic_cast转换成一个引用对象,如果出错,将是抛出异常。如果不做异常捕获,将导致我们程序崩溃。

        下面两段代码是安全的

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
	{
		Child* pChild = new (std::nothrow) Child();
		if (!pChild) {
			return;
		}
		pChild->print();
		pChild->printv();
		Parent* pParent = dynamic_cast<Parent*>(pChild);
		if (pParent) {										// Well
			pParent->print();				
			pParent->printv();
		}
		else {
			printf("dynamic_cast error\n");
		}

		try {
			Parent& p = dynamic_cast<Parent&>(*pChild);		// Well
			p.print();				
			p.printv();
		}
		catch (std::bad_cast& e) {
			printf("dynamic_cast error : %s\n", e.what()); 	// No Hit
		} 

		delete pChild;
	}

	{
		Parent* pChild = new (std::nothrow) Child();
		if (!pChild) {
			return;
		}
		pChild->print();
		pChild->printv();
		Parent* pParent = dynamic_cast<Parent*>(pChild);
		if (pParent) {										// Well
			pParent->print();				
			pParent->printv();
		}
		else {
			printf("dynamic_cast error\n");
		}

		try {
			Parent& p = dynamic_cast<Parent&>(*pChild);		// Well
			p.print();				
			p.printv();
		}
		catch (std::bad_cast& e) {
			printf("dynamic_cast error : %s\n", e.what()); 	// No Hit
		} 

		delete pChild;
	}
}

        一般来说,因为RTTI技术作用于运行时,所以其会产生运行时的代价。所以很多人建议,如果在能明确转换安全的场景下,不要使用dynamic_cast方法进行转换,而是使用static_cast,以免进行一些不必要的运行时计算。

const_cast

        const_cast与之前介绍的各种转换都不同,它只是负责给变量增加或者删除一些属性,以保证编译器可以编过。我们直接看例子

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void printf_without_const(char* pbuffer){
	if (!pbuffer || 0 == strlen(pbuffer)) {
		return;
	}
	*(pbuffer) = '$';						// can modify
	printf(pbuffer);
};

void const_cast_test() {

	{
		const char* pbuffer = "const_cast_test";
		char szbuf[64] = {0};
		strncat(szbuf, pbuffer, strlen(pbuffer));

		const char* psz = const_cast<const char*>(szbuf);
		//printf_without_const(psz);			// error

		char* p = const_cast<char*>(psz);
		printf_without_const(p);
	}
}

        总结一下:

  • 类C强制转换十分强大。因为它是二进制级别内存拷贝操作,所以可以大部分场景不会出现编译错误。但是如果用它去转换指针,可能会出现运行时错误
  • reinterpret_cast、static_cast和dynamic_cast主要用于指针的转换
  • reinterpret_cast的能力仅次于类C转换。虽然它约束了整型、浮点和枚举类型的相互转换,但是还是支持指针和整型的转换。它也存在转换后运行时出错的隐患
  • static_cast弥补了reinterpret_case对整型、浮点和枚举类型的相互转换的功能。除了这些转换外,它要求操作的参数是指针。且已经出现C++特性限制,要求指针转换时的类存在继承关系(void*除外)。它也存在转换后运行时出错的隐患
  • dynamic_cast已经是纯的C++特性转换,使用到了RTTI技术。于是它要求操作的指针类型具有多态特性。它解决了指针转换后使用出现运行时出错的问题,但是使用该方法要付出运行时计算的代价如果能明确转换是安全的,建议使用static_cast方法(不使用reinterpret_cast是因为它还没体现出C++的特性)。
  • const_cast用于增加和去掉一些类型描述标志,如const等。

参考文献:http://www.cplusplus.com/doc/tutorial/typecasting/

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2014年12月16日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
开源项目 requests 的 stars 为啥比 python 还多 3.7k?
结合上一篇文章《一次算法读图超时引起的urllib3源码分析》,我们学习了 urllib3 的基本语法、常见姿势和请求管理模式,以及PoolManager、HTTPConnectionPool、HTTPConnection等模块部分源码。对于学习 Python 的小伙伴来说,urllib3 强大的功能几乎能实现所有 HTTP 请求场景,但这就足够了吗?
程序员荒生
2022/03/15
8030
开源项目 requests 的 stars 为啥比 python 还多 3.7k?
requests-html HTTPSConnectionPool
HTTPSConnectionPool(host='hk.zaful.com', port=443): Read timed out. (read timeout=0.1)
py3study
2020/01/06
6570
python3的request用法实例
requests 是爬取数据最常用的模块,比起 urllib, urllib2, urllib3 这几个单是看名字就晕的模块,requests 不仅功能强大,而且 api 简单易用,使用起来有如丝般顺滑
章鱼喵
2019/08/24
2.8K0
多线程爬虫使用代理IP指南
多线程爬虫能有效提高工作效率,如果配合代理IP爬虫效率更上一层楼。作为常年使用爬虫做项目的人来说,选择优质的IP池子尤为重要,之前我讲过如果获取免费的代理ip搭建自己IP池,虽然免费但是IP可用率极低。
华科云商小徐
2025/06/04
310
为什么说每个爬虫工程师都要掌握 retry 装饰器
今天介绍一个实用的 python 库:retrying,它通过装饰器方法 retry 抽象出业务无关的重试机制实现,可以快速引入我们的工程中提高代码的健壮性和鲁棒性。
月小水长
2024/12/23
1600
Requests库详解
requests(爬虫系列之一) 由于最近工作中,与同事对接模拟手机浏览器进行广告模拟跳转。又一次接触用到爬虫的知识,以前用过urllib + bs4 + selenium定向爬取网易一元夺宝的商品信息保存在数据库中,当时,还是太年轻,对爬虫不是很了解,对爬虫的robots协议也不知道。现在重新梳理一下爬虫的知识。争取写一个系列,大致内容顺序是requests, bs4,re, scrapy, selenium等。 在介绍requests库之前,先介绍以下基本的http概念, 下面内容是在上嵩天教授课程
若与
2018/04/25
2K1
Requests库详解
漫漫人生路总会错几步python之retry
如果自动化中失败了一次,我们给机会了,成功了就既往不咎。只能说明不够稳定。那么retry该如何写呢?
赵云龙龙
2019/07/11
7300
漫漫人生路总会错几步python之retry
再谈装饰器
昨天我分享了装饰器的使用方法,发现看的人并不多,这也正常,毕竟装饰器是一种锦上添花的东西,没有它,无法稍微麻烦点,但还是可以凑合着过的。
somenzz
2020/11/25
4070
HTTP协议栈远程代码执行漏洞(CVE-2022-21907)复现
HTTP协议堆栈中存在远程代码执行漏洞,由于HTTP协议栈(HTTP.sys)中的HTTP Trailer Support功能存在边界错误可导致缓冲区溢出。
洛米唯熊
2022/01/23
2.3K0
requests-代理设置,超时设置,登陆验证,Prepared Requests
对于某些网站,大规模频繁请求,网站可能会弹出验证码,或者跳转到登陆认证页面,甚至可能会被直接封客户端ip,导致短时间内无法访问,这个时候就需要用到代理ip。
py3study
2020/02/10
2.8K0
python语言中的AOP利器:装饰器
一、前言 面向切面编程(AOP)是一种编程思想,与OOP并不矛盾,只是它们的关注点相同。面向对象的目的在于抽象和管理,而面向切面的目的在于解耦和复用。 举两个大家都接触过的AOP的例子: 1)java中mybatis的@Transactional注解,大家知道被这个注解注释的函数立即就能获得DB的事务能力。 2)python中的with threading.Lock(),大家知道,被这个with代码块包裹的部分立即获得同步的锁机制。 这样我们把事务和加锁这两种与业务无关的逻辑抽象出来,在逻辑上解耦,并且可以
用户1225216
2018/03/29
2.2K0
python接口自动化29-requests超时重试方法
“由于连接方在一段时间后没有正确答复或连接的主机没有反应,连接尝试失败”,这是经常遇到的问题 requests.exceptions.ConnectionError: HTTPSConnectionPool(host=’www.github.com’, port=443): Max retries exceeded with url: / (Caused by NewConnectionError(‘<urllib3.connection.verifiedhttpsconnection object="" at="" 0x0000020f06524ac8="">: Failed to establish a new connection: [WinError 10060] 由于连接方在一段时间后没有正确答复或连接的主机没有反应,连接尝试失败。’,)) 一般出现这个问题的原因是:host=’www.github.com’ 主机地址没连上,使用 requests 发请求时,有些网站服务器不稳定,特别是国外的网站,经常会出现连接失败情况。 连接失败后,有时候会抛出上面异常,有时候会一直卡住,进入假死状态,没响应,也不会结束。</urllib3.connection.verifiedhttpsconnection>
上海-悠悠
2020/05/29
5.7K0
Python函数超时,用装饰器解决
我们在自定义一个函数后,会调用这个函数来完成我们想要的功能。 就拿爬虫来举例,你发送请求,服务器给你响应,但是有可能服务器没有给你任何数据,无论是他识别了爬虫、还是服务器繁忙什么原因,这个时候,你的爬虫就会一直等待响应,这个时候就会非常浪费资源,还会造成程序阻塞。
小歪
2018/07/25
2.5K0
Python函数超时,用装饰器解决
懒人必备,五个高效Python装饰器
虽然我已经写了很多的代码,但除非绝对必要,我很少使用装饰器,比如使用@staticmethod装饰器来表示一个类中的静态方法。
数据STUDIO
2023/09/18
3810
Python 装饰器
概念:是一个闭包,把一个函数作为参数然后返回一个替代版的函数,本质上是一个返回函数的函数
星哥玩云
2022/09/08
3300
一日一技:方法不对,代码翻倍。Requests如何正确重试?
我们在做Python开发时,经常使用一些第三方库,这些库很多年来持续添加了新功能。但我发现很多同学在使用这些第三方库时,根本不会使用新的功能。他们的代码跟几年前没有任何区别。
青南
2023/08/21
7550
一日一技:方法不对,代码翻倍。Requests如何正确重试?
python模块之requests及应用
Python标准库中提供了:urllib、urllib2、httplib等模块以供Http请求,但是,它的 API 太渣了。它是为另一个时代、另一个互联网所创建的。它需要巨量的工作,甚至包括各种方法覆盖,来完成最简单的任务。
菲宇
2019/06/12
1.6K0
python模块之requests及应用
Python装饰器怎么做重试机制
重试机制在编程中是比较常见的场景,主要被用于处理那些可能由于临时性故障或网络波动等原因而失败的操作。
阿珍
2024/07/12
1240
Python装饰器怎么做重试机制
python使用retrying重试请求
当我们用 request 发起网络请求,时不时会遇到超时,当然不可能让这个请求一直阻塞,一般会设置一个超时时间,用 try except 抛出异常,避免程序中断。可如果一次超时就放弃该请求,误杀的概率会很大,我们日常访问某网站时,有打不开的情况都会多刷新几次。因此,我们也需要让 python 进行重试。而 retrying 模块应运而生
章鱼喵
2019/08/24
1.3K0
10个简单但很有用的Python装饰器
装饰器(Decorators)是Python中一种强大而灵活的功能,用于修改或增强函数或类的行为。装饰器本质上是一个函数,它接受另一个函数或类作为参数,并返回一个新的函数或类。它们通常用于在不修改原始代码的情况下添加额外的功能或功能。
deephub
2023/08/30
3150
10个简单但很有用的Python装饰器
推荐阅读
相关推荐
开源项目 requests 的 stars 为啥比 python 还多 3.7k?
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档