C++ 强制类型转换

static_cast<>揭密(转)

原文链接:What static_cast<> is actually doing

本文讨论static_cast<> 和 reinterpret_cast<>

介绍
大多程序员在学C++前都学过C,并且习惯于C风格(类型)转换。当写C++(程序)时,有时候我们在使用static_cast<>reinterpret_cast<>时可能会有点模糊。在本文中,我将说明static_cast<>实际上做了什么,并且指出一些将会导致错误的情况。

泛型(Generic Types

float f = 12.3;
float* pf = &f;

// static cast 
// 成功编译, n = 12
int n = static_cast(f);
// 错误,指向的类型是无关的(译注:即指针变量pffloat类型,现在要被转换为int类型) //int* pn = static_cast(pf);
//成功编译
void* pv = static_cast(pf);
//成功编译但是 *pn2是无意义的内存(rubbish
int* pn2 = static_cast(pv);

// reinterpret_cast 
//错误,编译器知道你应该调用static_cast 
//int i = reinterpret_cast(f);
//成功编译但是 *pn 实际上是无意义的内存,和 *pn2一样
int* pi = reinterpret_cast(pf);

简而言之,static_cast<> 将尝试转换,举例来说,如float--integer,而reinterpret_cast<>简单改变编译器的意图重新考虑那个对象作为另一类型。

指针类型(Pointer Types

指针转换有点复杂,我们将在本文的剩余部分使用下面的类:
class CBaseX
{
public:
int x;
CBaseX() { x = 10; }
void foo() { printf("CBaseX::foo() x=%d\n", x); }
};

class CBaseY
{
public:
int y;
int* py;
CBaseY() { y = 20; py = &y; }
void bar() { printf("CBaseY::bar() y=%d, *py=%d\n", y, *py); }
};

class CDerived : public CBaseX, public CBaseY
{
public:
int z;
};

情况1:两个无关的类之间的转换

// Convert between CBaseX* and CBaseY*
// CBaseX* 和 CBaseY*之间的转换
CBaseX* pX = new CBaseX();
// Error, types pointed to are unrelated
// 错误, 类型指向是无关的
// CBaseY* pY1 = static_cast(pX);
// Compile OK, but pY2 is not CBaseX
// 成功编译但是 pY2 不是CBaseX
CBaseY* pY2 = reinterpret_cast(pX);
// System crash!!
// 系统崩溃!!
// pY2->bar();正如我们在泛型例子中所认识到的,如果你尝试转换一个对象到另一个无关的类static_cast<>将失败,而reinterpret_cast<>就总是成功“欺骗”编译器:那个对象就是那个无关类。

情况2:转换到相关的类
1. CDerived* pD = new CDerived();
2. printf("CDerived* pD = %x\n", (int)pD);
3. 
4. // static_cast CDerived* -> CBaseY* -> CDerived*
//成功编译,隐式static_cast 转换
5. CBaseY* pY1 = pD;
6. printf("CBaseY* pY1 = %x\n", (int)pY1);
// 成功编译现在 pD1 = pD
7. CDerived* pD1 = static_cast(pY1);
8. printf("CDerived* pD1 = %x\n", (int)pD1);
9. 
10. // reinterpret_cast
// 成功编译但是 pY2 不是 CBaseY*
11. CBaseY* pY2 = reinterpret_cast(pD);
12. printf("CBaseY* pY2 = %x\n", (int)pY2);
13. 
14. // 无关的 static_cast 
15. CBaseY* pY3 = new CBaseY();
16. printf("CBaseY* pY3 = %x\n", (int)pY3);
// 成功编译,尽管 pY3 只是一个 "新 CBaseY()"
17. CDerived* pD3 = static_cast(pY3);
18. printf("CDerived* pD3 = %x\n", (int)pD3);---------------------- 输出 ---------------------------
CDerived* pD = 392fb8
CBaseY* pY1 = 392fbc
CDerived* pD1 = 392fb8
CBaseY* pY2 = 392fb8
CBaseY* pY3 = 390ff0
CDerived* pD3 = 390fec

注意:在将CDerived*用隐式 static_cast<>转换到CBaseY*(第5行)时,结果是(指向)CDerived*(的指针向后) 偏移了4(个字节)(译注:4int类型在内存中所占字节数)。为了知道static_cast<> 实际如何,我们不得不要来看一下CDerived的内存布局。

CDerived的内存布局(Memory Layout

 

如图所示,CDerived的内存布局包括两个对象,CBaseX 和 CBaseY,编译器也知道这一点。因此,当你将CDerived* 转换到 CBaseY*时,它给指针添加4个字节,同时当你将CBaseY*转换到CDerived*时,它给指针减去4。然而,甚至它即便不是一个CDerived你也可以这样做。
当然,这个问题只在如果你做了多继承时发生。在你将CDerived转换 到 CBaseXstatic_cast<> 和 reinterpret_cast<>是没有区别的。

情况3void*之间的向前和向后转换

因为任何指针可以被转换到void*,而void*可以被向后转换到任何指针(对于static_cast<> 和 reinterpret_cast<>转换都可以这样做),如果没有小心处理的话错误可能发生。

CDerived* pD = new CDerived();
printf("CDerived* pD = %x\n", (int)pD);

CBaseY* pY = pD; // 成功编译, pY = pD + 4
printf("CBaseY* pY = %x\n", (int)pY);

void* pV1 = pY; //成功编译, pV1 = pY
printf("void* pV1 = %x\n", (int)pV1);

// pD2 = pY, 但是我们预期 pD2 = pY - 4
CDerived* pD2 = static_cast(pV1);
printf("CDerived* pD2 = %x\n", (int)pD2);
// 系统崩溃
// pD2->bar();

---------------------- 输出 ---------------------------
CDerived* pD = 392fb8
CBaseY* pY = 392fbc
void* pV1 = 392fbc
CDerived* pD2 = 392fbc

一旦我们已经转换指针为void*,我们就不能轻易将其转换回原类。在上面的例子中,从一个void* 返回CDerived*的唯一方法是将其转换为CBaseY*然后再转换为CDerived*。 
但是如果我们不能确定它是CBaseY* 还是 CDerived*,这时我们不得不用dynamic_cast<> typeid[2]

注释:
1. dynamic_cast<>,从另一方面来说,可以防止一个泛型CBaseY* 被转换到CDerived*
2. dynamic_cast<>需要类成为多态,即包括“虚”函数,并因此而不能成为void*
参考: 
1. [MSDN] C++ Language Reference -- Casting 
2. Nishant Sivakumar, Casting Basics - Use C++ casts in your VC++.NET programs 
3. Juan Soulie, C++ Language Tutorial: Type Casting
推荐链接:如何在运行时确定对象类型(RTTI

话外语:

强制类型转换容易引发错误,所以被认为是一种丑陋的语法。C++中有一个观念:丑陋的语法就应该用丑陋的方式表达。所以,C++的转换操作符使用xxxx_cast<...>()这种一种繁琐的形式。目的是为了让你在使用类型转换前三思而行,看看是不是又不需要类型转换的方法或设计。
更重要的是,C风格的强制类型转换通常很难查找。而C++这四种转换操作符可以用最简单的search功能找到。所以更便于排错。
这就是说,只要你在C++中编程,尽量不要使用类型转换,即使要用,也应该使用xxxx_cast<..>()这种形式。
四种转换的大概解释如下:
dynamic_cast    用于多态类型间的转换。包括继承类间的转换和兄弟类间的转换。
static_cast    用于非多态类型的转换。既不具有继承关系的类型间的转换。
const_cast    用于移除const, volatile限定符.
reinterpret_cast    用于任意的位到位的转换.

 

 

C++中的四种强制转换 dynamic_case,const_cast,static_case,reinterprer_case的不同


c++classobjectdelete编程null

使用标准C++的类型转换符:static_castdynamic_castreinterpret_cast、和const_cast

1 static_cast
用法:static_cast < type-id > ( expression )     
该运算符把expression转换为type-id类型,但没有运行时类型检查来保证转换的安全性。它主要有如下几种用法:
①用于类层次结构中基类和子类之间指针或引用的转换。
  进行上行转换(把子类的指针或引用转换成基类表示)是安全的;
  进行下行转换(把基类指针或引用转换成子类表示)时,由于没有动态类型检查,所以是不安全的。
②用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证。
③把空指针转换成目标类型的空指针。
④把任何类型的表达式转换成void类型。

注意:static_cast不能转换掉expressionconstvolitale、或者__unaligned属性。


2 dynamic_cast
用法:dynamic_cast < type-id > ( expression )
该运算符把expression转换成type-id类型的对象。Type-id必须是类的指针、类的引用或者void *
如果type-id是类指针类型,那么expression也必须是一个指针,如果type-id是一个引用,那么expression也必须是一个引用。

dynamic_cast主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换。
在类层次间进行上行转换时,dynamic_caststatic_cast的效果是一样的;
在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。
class B{
public:
       int m_iNum;
       virtual void foo();
};

class D:public B{
    public:
       char *m_szName[100];
};

void func(B *pb){
    D *pd1 = static_cast<D *>(pb);
    D *pd2 = dynamic_cast<D *>(pb);
}

在上面的代码段中,如果pb指向一个D类型的对象,pd1pd2是一样的,并且对这两个指针执行D类型的任何操作都是安全的;
但是,如果pb指向的是一个B类型的对象,那么pd1将是一个指向该对象的指针,对它进行D类型的操作将是不安全的(如访问m_szName),
pd2将是一个空指针。

另外要注意:B要有虚函数,否则会编译出错;static_cast则没有这个限制。
这是由于运行时类型检查需要运行时类型信息,而这个信息存储在类的虚函数表(
关于虚函数表的概念,详细可见<Inside c++ object model>)中,只有定义了虚函数的类才有虚函数表,
没有定义虚函数的类是没有虚函数表的。

另外,dynamic_cast还支持交叉转换(cross cast)。如下代码所示。
class A{
public:
        int m_iNum;
        virtual void f(){}
};

class B:public A{
};

class D:public A{
};

void foo(){
    B *pb = new B;
    pb->m_iNum = 100;

    D *pd1 = static_cast<D *>(pb);    //compile error
    D *pd2 = dynamic_cast<D *>(pb); //pd2 is NULL
    delete pb;
}

在函数foo中,使用static_cast进行转换是不被允许的,将在编译时出错;而使用 dynamic_cast的转换则是允许的,结果是空指针。


3 reinterpret_cast
用法:reinterpret_cast<type-id> (expression)
type-id必须是一个指针、引用、算术类型、函数指针或者成员指针。
它可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针(先把一个指针转换成一个整数,
在把该整数转换成原类型的指针,还可以得到原先的指针值)。

该运算符的用法比较多。

(static_cast .. reinterpret_cast比较,见下面 )

4 const_cast
用法:const_cast<type_id> (expression)
该运算符用来修改类型的constvolatile属性。除了const volatile修饰之外, type_idexpression的类型是一样的。
常量指针被转化成非常量指针,并且仍然指向原来的对象;
常量引用被转换成非常量引用,并且仍然指向原来的对象;常量对象被转换成非常量对象。

Voiatileconst类试。举如下一例:
class B{
public:
     int m_iNum;
}
void foo(){
const B b1;
b1.m_iNum = 100;            //comile error
B b2 = const_cast<B>(b1);
b2. m_iNum = 200;           //fine
}
上面的代码编译时会报错,因为b1是一个常量对象,不能对它进行改变;
使用const_cast把它转换成一个常量对象,就可以对它的数据成员任意改变。注意:b1b2是两个不同的对象。


== ===========================================

== dynamic_cast .vs. static_cast 
== ===========================================

class B { ... };
class D : public B { ... };

void f(B* pb)
{

D* pd1 = dynamic_cast<D*>(pb);

D* pd2 = static_cast<D*>(pb); 
}



If pb really points to an object of type D, then pd1 and pd2 will get the same value. They will also get the same value if pb == 0. 



If pb points to an object of type B and not to the complete D class, then dynamic_cast will know enough to return zero. However, static_cast relies on the programmers assertion that pb points to an object of type D and simply returns a pointer to that supposed D object. 


dynamic_cast可用于继承体系中的向下转型,即将基类指针转换为派生类指针,比static_cast更严格更安全。 dynamic_cast在执行效率上比static_cast要差一些,但static_cast在更宽上范围内可以完成映射,这种不加限制的映射伴随 着不安全性。static_cast覆盖的变换类型除类层次的静态导航以外,还包括无映射变换、窄化变换(这种变换会导致对象切片,丢失信息)、用 VOID*的强制变换、隐式类型变换等... 


== ===========================================
== static_cast .vs. reinterpret_cast
== ================================================


reinterpret_cast是为了映射到一个完全不同类型的意思,这个关键词在我们需要把类型映射回原有类型时用到它。我们映射到的类型仅仅是为了故弄玄虚和其他目的,这是所有映射中最危险的。(这句话是C++编程思想中的原话)

static_cast 和 reinterpret_cast 操作符修改了操作数类型。它们不是互逆的; static_cast 在编译时使用类型信息执行转换,在转换执行必要的检测(诸如指针越界计算类型检查). 其操作数相对是安全的。另一方面;reinterpret_cast 仅仅是重新解释了给出的对象的比特模型而没有进行二进制转换, 例子如下:
int n=9; double d=static_cast < double > (n);


上面的例子中我们将一个变量从 int 转换到 double。 这些类型的二进制表达式是不同的。 要将整数 转换到 双精度整数 9static_cast 需要正确地为双精度整数 补足比特位。其结果为 9.0。而reinterpret_cast 的行为却不同

int n=9;

double d=reinterpret_cast<double & > (n);

这次结果有所不同在进行计算以后, d 包含无用值这是因为 reinterpret_cast 仅仅是复制 的比特位到 d, 没有进行必要的分析.

因此你需要谨慎使用 reinterpret_cast.

 

C++的强制类型转换

c/c++强制类型转换

Q:什么是C风格转换?什么是static_cast, dynamic_cast 以及 reinterpret_cast?区别是什么?为什么要注意?
A:转换的含义是通过改变一个变量的类型为别的类型从而改变该变量的表示方式。为了类型转换一个简单对象为另一个对象你会使用传统的类型转换操作符。比如,为了转换一个类型为doubole的浮点数的指针到整型:
代码:
int i;
double d;
i = (int) d;或者:i = int (d);

对于具有标准定义转换的简单类型而言工作的很好。然而,这样的转换符也能不分皂白的应用于类(class)和类的指针。ANSI-C++标准定义了四个新的转换符:'reinterpret_cast', 'static_cast', 'dynamic_cast' 和 'const_cast',目的在于控制类(class)之间的类型转换。
代码:

· reinterpret_cast<new_type>(expression)

· dynamic_cast<new_type>(expression)

· static_cast<new_type>(expression)

· const_cast<new_type>(expression)

 

1 reinterpret_cast

reinterpret_cast 转换一个指针为其它类型的指针。它也允许从一个指针转换为整数类型。反之亦然。(译注:是指针具体的地址值作为整数值?)
这个操作符能够在非相关的类型之间转换。操作结果只是简单的从一个指针到别的指针的值的二进制拷贝。在类型之间指向的内容不做任何类型的检查和转换。如果情况是从一个指针到整型的拷贝,内容的解释是系统相关的,所以任何的实现都不是方便的。一个转换到足够大的整型能够包含它的指针是能够转换回有效的指针的。
代码:
class A {};
class B {};
A * a = new A;
B * b = reinterpret_cast<B *>(a);
reinterpret_cast  就像传统的类型转换一样对待所有指针的类型转换。

2 static_cast

static_cast 允许执行任意的隐式转换和相反转换动作。(即使它是不允许隐式的)
意思是说它允许子类类型的指针转换为父类类型的指针(这是一个有效的隐式转换),同时,也能够执行相反动作:转换父类为它的子类
在这最后例子里,被转换的父类没有被检查是否与目的类型相一致。
代码:
class Base {};
class Derived : public Base {};

Base *a    = new Base;
Derived *b = static_cast<Derived *>(a);
static_cast  除了操作类型指针,也能用于执行类型定义的显式的转换,以及基础类型之间的标准转换:
代码:
double d = 3.14159265;
int    i = static_cast<int>(d);

3 dynamic_cast

dynamic_cast只用于对象的指针和引用。当用于多态类型时,它允许任意的隐式类型转换以及相反过程。不过,与static_cast不同,在后一种情况里(注:即隐式转换的相反过程),dynamic_cast会检查操作是否有效。也就是说,它会检查转换是否会返回一个被请求的有效的完整对象。
检测在运行时进行。如果被转换的指针不是一个被请求的有效完整的对象指针,返回值为NULL.
代码:
class Base { virtual dummy() {} };
class Derived : public Base {};

Base* b1 = new Derived;
Base* b2 = new Base;

Derived* d1 = dynamic_cast<Derived *>(b1);          // succeeds
Derived* d2 = dynamic_cast<Derived *>(b2);          // fails: returns 'NULL'

如果一个引用类型执行了类型转换并且这个转换是不可能的,一个bad_cast的异常类型被抛出:
代码:
class Base { virtual dummy() {} };
class Derived : public Base { };

Base* b1 = new Derived;
Base* b2 = new Base;

Derived d1 = dynamic_cast<Derived &*>(b1);          // succeeds
Derived d2 = dynamic_cast<Derived &*>(b2);          // fails: exception thrown

4 const_cast

这个转换类型操纵传递对象的const属性,或者是设置或者是移除:
代码:
class C {};
const C *a = new C;

C *b = const_cast<C *>(a);
其它三种操作符是不能修改一个对象的常量性的。
注意:'const_cast'也能改变一个类型的volatile qualifier。

 C++的四种强制转型形式每一种适用于特定的目的: 

  ·dynamic_cast 主要用于执行“安全的向下转型(safe downcasting)”,也就是说,要确定一个对象是否是一个继承体系中的一个特定类型。它是唯一不能用旧风格语法执行的强制转型,也是唯一可能有重大运行时代价的强制转型。
    
    ·static_cast 可以被用于强制隐型转换(例如,non-const 对象转型为 const 对象,int 转型为 double,等等),它还可以用于很多这样的转换的反向转换(例如,void* 指针转型为有类型指针,基类指针转型为派生类指针),但是它不能将一个 const 对象转型为 non-const 对象(只有 const_cast 能做到),它最接近于C-style的转换。
    
  ·const_cast 一般用于强制消除对象的常量性。它是唯一能做到这一点的 C++ 风格的强制转型。 

  ·reinterpret_cast 是特意用于底层的强制转型,导致实现依赖(implementation-dependent)(就是说,不可移植)的结果,例如,将一个指针转型为一个整数。这样的强制转型在底层代码以外应该极为罕见。
  

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值