提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
我觉得C++里面有一个很神奇的转换叫reinterpret_cast(类型不相关转换),它和诸如static_cast、dynamic_cast都不一样,这种转换有时候让人摸不到头脑,但是有些场景非常有用。
一、什么是reinterpret_cast
简而言之,类型不相关的转换。但是不是说可以随便转换,它主要用在指针与指针之间,指针和long之间等等。必要条件是两种类型之间必须有相同的位模式,而且转换过后不能直接拿来用,必须转换回来才能使用。
二、代码测试
//返回转换结果,long型
long test_reinterpret_cast() {
int *a = new int{2};
long b = reinterpret_cast<long>(a);
return b;
}
int main(){
long b = test_reinterpret_cast();
int *c = reinterpret_cast<int *>(b);
cout << "c = " << *c << endl;
delete c;
return 0;
}
注意上面的方法,按照平常的写法是不是就memory leak
了。很多聪明的IDE会提示你int* a这个地方内存泄漏了,事实真的如此吗?
这就不得不说reinterpret_cast的强大之处了,因为b和a拥有相同的位模式
,所以转换是有效的。当我把b转换回a(或c,其实就是内存地址)的时候我又重新指向new出来的内存了,这个时候delete c就回收内存了。使用valgrind测试,没有出现内存溢出的情况。
注意:如果把b换成int类型就不行了,int类型和int*在64bits系统上没有相同的位模式,因为int*在64bits系统上占8个字节,int在64bits系统上占4个字节,所以会报错,long型在64bits系统上也占8个字节。
三、垃圾回收器的困扰
可能很多人不知道C++其实预留了垃圾回收接口,不过实现起来很复杂,还可能会出现意想不到的情况。比如上面的reinterpret_cast就可能成为阻挠垃圾回收的罪魁祸首。我们回过头来再看下上面的代码:
//返回转换结果,long型
long test_reinterpret_cast() {
int *a = new int{2};
long b = reinterpret_cast<long>(a);
return b;
}
int main(){
long b = test_reinterpret_cast();
int *c = reinterpret_cast<int *>(b);
cout << "c = " << *c << endl;
delete c;
return 0;
}
return b;
之后就没有指针指向int* a
所指向的内存地址了,按道理说是不是这块内存要被垃圾回收器回收了。现实是我们保存在long b
里面了,垃圾回收器并不知道
,还是照样回收。等到代码执行到int *c = reinterpret_cast<int *>(b);
这句的时候,是不是转换结果就失效了。这个时候强行访问这块内存就会报非法内存错误
。
四、使用场景
不说C++源代码到处是reinterpret_cast了,好不好用还有谁比专家更清楚。
除了这些还有其它场景,如果你看过诸如Android操作系统源代码
或OpenCV的源代码
你就会知道,reinterpret_cast用的简直不要太多。我举一个例子你就懂了,我们都知道OpenCV为了获得高性能底层大量使用C++开发。如果你在Java层使用OpenCV它是不是要调用C++层的东西,我们都知道JNI就是这个桥梁。但是,如果我问题你怎么完成Java对象和C++对象之间的传递和转换呢?Java对象和C++对象可是拥有着不同的内存结构的,难道你要跨语言转换对象吗?
聪明的开发者已经想到了,当然是用我们的reinterpret_cast了,我可以把C++的对象转换成long型传回Java层,这个long你可以理解为就是指向C++层创建的对象的内存地址,等到合适的时机我把它转换回来就行了。然后Java层也创建一个Java对象里面保存long、rows、cols等等必要的消息,只有需要运算的时候我才把long传下去转换,只需要很小的代价这就解决了这个问题,比起巨大的性能提升这点内存还是微不足道的。
由于C++是手动回收内存的,你永远不必担心这块内存会丢失,直到你不再需要它为止。
总结
1、研究了很久还是决定拿出来掰扯掰扯。