C++类型转换

这篇介绍C++的4种类型转换 dynamic_cast, static_cast, reinterpret_cast和const_cast。

1 dynamic_cast


从名字可以看出这种转换是动态发生的,其转化结果是可靠的;这种转换通常用在父类和子类之间的转化。

  • 如果从父类到子类,那么要求父类必须有虚函数。
  • 如果从子类到父类,dynamic_cast实际的功能和static_cast是一样的,因而也不需要类的定义有虚函数。

所谓动态就是体现在虚函数表上,因为在转换时需要从虚函数表动态获取类的信息。

2 static_cast


相对动态转换而言,所谓静态转换,是指转换发生在编译器编译的时刻,而不是程序运行的时刻,那么这种转化不需要类的虚函数表,因而转换结果也是不可靠的,需要程序员自己保证转换后是否有效。

  • 和动态转换一样,静态转换也能用来在父类和子类之间。
    1. 从子类到父类的转换是安全的
      实际上dyanmic_cast在处理子类到父类的转换时使用的就是static_cast的逻辑,即这两者是一样的,这就说明即使是dynamic_cast在处理这类转换时也不需要类有虚函数表了。
  1. 而从父类到子类的转换时不安全的
    相比较dynamic_cast在这类转换时安全的,即如果转换成功则返回一个有效指针,而转换不成功则返回空指针,程序员可以依据返回值判断转换是否成功;而static_cast则没有这个功能,返回值均不为空,这样就无法保证转换过程是否正确,必须由程序员来保障,好处是这类转化不需要基类有虚函数表,比如当你的类继承关系中没有虚函数表时而又需要做类型转换时使用。
  • 此外静态转换还能处理基本数据类型之间的转换

比如char, int, float, double等

int i = 1;
double d = static_cast<double>(1);

再次讨论下动态转换和静态转换。
动态转换发生在程序运行时刻,而静态转换发生在编译时刻,编译时只能进行静态检查,无法确定真实运行时刻的信息,我们看下汇编代码就很清楚:

#include <string>
#include <typeinfo>

class A {
public:
    virtual ~A() {}
};

class B : public A {
public:
    virtual ~B() {}
};

void foo(A * a) {
    B * b1 = dynamic_cast<B *>(a);
    B * b2 = static_cast<B *>(a);
}

int main(int argc, char * argv[]) {
    B * b = new B();
    foo(b);
    return 0;

函数foo代码把参数A *a转换成B *,一次使用动态转换,一次使用静态转换;生成的foo函数汇编代码如下:

    pushq   %rbp
    movq    %rsp, %rbp
    subq    $32, %rsp
    movq    %rdi, -24(%rbp)
    movq    -24(%rbp), %rax
    testq   %rax, %rax
    jne .L16
    movl    $0, %eax
    jmp .L17
.L16:
    movl    $0, %ecx
    movl    $_ZTI1B, %edx
    movl    $_ZTI1A, %esi
    movq    %rax, %rdi
    call    __dynamic_cast
.L17:
    movq    %rax, -16(%rbp)
    movq    -24(%rbp), %rax
    movq    %rax, -8(%rbp)
    leave
    ret

对于动态转换,首先检查参数A * a是否为null,如果为null则无法继续转换,返回null表示转换失败,如果不为null则调用C++系统库函数__dynamic_cast完成转换;而对于静态准换,编译器直接把A * a的指针move给了B * b2的指针,没有做任何检查和转换,只在编译的时候编译器检查两者的声明类型是否匹配能否转换。

当然如果是两个不相关的类型,那么也是不能使用static_cast进行转换的,因为编译器在编译的时候检查了两者类型是否合适,比如:

class A {
public:
    virtual ~A() {}
};

class B {
public:
    virtual ~B() {}
};

void foo(A * a) {
    B * b = static_cast<B *>(a);
}

上述A和B没有任何关系,编译器在编译时就报错:

t.cpp: In function 'void foo(A*)':
t.cpp:<line>: error: invalid static_cast from type 'A*' to type 'B*'

因为A和B根本就是两个风牛马不相及的类;但是另一个reinterpret_cast却可以做到。

3 reinterpret_cast


这类转换很好理解,就是重新解析了内存比特位,不管你的内存内容是什么,全部按照目标类的内存声明是套。
可想而知,这种转换是一点安全也没有的,任意转换,任意解析,连编译器静态检查都没有,必须由程序员完成控制。如果原类型和目标类型的内存机构一样转换是没有问题的,如果稍有不一样,后果是不可预知的,程序crash就很容易出现。举一个例子:

#include <stdio.h>
#include <string>
#include <typeinfo>

class A {
public:
    int i;
    int j;
public:
    void foo() { printf("A::foo(): i=%d,j=%d\n", i, j); }
};

class B {
public:
    int j;
    int i;
public:
    void foo() { printf("B::foo(): i=%d,j=%d\n", i, j); }
};

void foo(A * a) {
    a->foo();
    B * b = reinterpret_cast<B *>(a);
    b->foo();
}

int main(int argc, char * argv[]) {
    A * a = new A();
    a->i = 100;
    a->j = 200;
    foo(a);
    return 0;
}

类A和类B没有任何继承关系,A和B都定义了两个变量i 和j,区别就是A先定义i,再定义j;而B相反先定义j,再定义i,我们运行结果:

A::foo(): i=100,j=200
B::foo(): i=200,j=100

在B:foo输出里面颠倒了A中定义的i和j,为什么呢,因为B的定义中内存布局是先j在i的,当我们把同样的一块A内存重新解析成了B内容,前四个字节就是存储j的值,后四个字节存储i的值。

4 const_cast


最后一种转换,很简单就是去除const属性:

#include <stdio.h>
#include <string>
#include <typeinfo>

class A {
};

class B {
};

void foo() {
          A * a1 = new A();
    const A * a2 = new A();

    a1 = a2;
}

int main(int argc, char * argv[]) {
    foo();
    return 0;
}

编译器报错:

t.cpp: In function 'void foo()':
t.cpp:<line>: error: invalid conversion from 'const A*' to 'A*'

改正办法就是使用const_cast,去除一个变量的const属性。
a1 = const_cast<A *>(a2);

考虑另外一个问题,const_cast能不能增加const属性呢?还是上面例子:

void foo() {
          A * a1 = new A();
    const A * a2 = new A();

    a2 = a1;
    a2 = const_cast<A *>(a1);
}

编译没有报任何错误,可见const_cast也可以用来增加const属性,但其实这个作用没什么用处,a2 = a1就可以实现,编译器也没有报错;试想把一个不可更改对象改成一个可更改对象是有风险的,对象的属性会发生变化,而反过来把一个可变对象改成一个不可变对象其实是没有风险的,不会对对象本身发生变化。


另外补充一点,除了dynamic_cast以外,其他的转换都是静态转换,也就是由编译器在编译时刻完成转换,在编译器生成的汇编代码里已经没有任何转换信息了;编译器只在编译时根据静态声明类型判断能够完成转换,如果编译器认为可以完成转换,那么通常会把原对象地址直接move到目标对象使用。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容

  • C++类型转换总结 本章内容:1 前言2 static_cast3 dynamic_cast4 const_cas...
    Haley_2013阅读 948评论 0 50
  • 前言 把《C++ Primer》[https://book.douban.com/subject/25708312...
    尤汐Yogy阅读 9,505评论 1 51
  • 隐式类型转换: C++的隐式转换发生在以下四种情况: 在混合类型的算术表达式中。 在表达式赋值中。 表达式传给函数...
    CapJon阅读 629评论 1 2
  • C++ 一共有4种类型转换方式,分别是: static_cast,dynamic_cast,const_cast,...
    wayyyy阅读 430评论 0 0
  • “老妈”儿子的一声叫喊,突然直戳心头。“这小子怎么突然叫我老妈了,我真的老了吗?”赶忙照照镜子,虽没那么老,但也绝...
    惠美惠阅读 269评论 0 2