最近项目为了效率,将一些地方的 dynamic_cast 优化成 static_cast,这次正好复习下 c++ 中的类型转换。
# 1.c++ 的类型转换
# i. 隐式类型转换
short a = 2000; | |
int b; | |
b = a; |
当用 short 类型的 a 给 int 类型的 b 赋值时,便会自动将 short 类型转换成 int。这种从小类型的整形往更大的整形的转换,就是一次 “提升”(promotion),从整形到浮点类型的转化也是一样。个人理解,“提升” 就是数值不会溢出转换,这种情况下 c++ 会自动隐式类型转换,并以相同的数值转换过去。
class A {}; | |
class B { | |
public: | |
// conversion from A (constructor): | |
B (const A& x) {} | |
// conversion from A (assignment): | |
B& operator= (const A& x) {return *this;} | |
// conversion to A (type-cast operator) | |
operator A() {return A();} | |
}; | |
int main () | |
{ | |
A foo; | |
B bar = foo; // calls constructor | |
bar = foo; // calls assignment | |
foo = bar; // calls type-cast operator | |
return 0; | |
} |
类通过重载赋值运算符,也可以实现隐式转换。
void fn(B bar){} | |
int main() | |
{ | |
A foo; | |
fn(foo); | |
} |
在传入函数时,若只声明了 B 类型参数的函数,也会发生隐式类型转换。
class B { | |
public: | |
explicit B (const A& x) {} | |
... | |
}; | |
int main () | |
{ | |
A foo; | |
B bar (foo); | |
bar = foo; | |
foo = bar; | |
// fn (foo); // not allowed for explicit ctor. | |
fn (bar); | |
return 0; | |
} |
但当转换的函数使用 explicit 关键词时,传参是就不能隐式转换了,需要显式地使用 = 符号或者构造函数的形式转换。
###ii. 显式类型转换
A* pFoo = new A(); | |
B* pBar = (B*)pFoo; |
(type) param,这是 c 中传统的类型转换,不过这种方式直接将 A 类的指针转换成了完全没有关系的 B 类的指针,但这种形式的转换在编译时并不会检查这次转换的合法性。为了防止这种不安全的类型转换,c++ 中新增了 4 种转换符。
显式类型转换有 4 种:
####(一).dynamic_cast
dynamic_cast 可以用于指针和引用的转换,它会保证本次转换的可靠性。
当从派生类指针转换到基类指针时,由于派生类一定是一个完成的基类,因此 dynamic_cast 不会做特别的检查。
但当使用它将基类转换为派生类时,它会根据 RTTI 去判断该基类指针是否可以作为一个完整的目标类。若本次转换不合法,则返回一个空指针。若引用转换不合法,则会抛出 bad_cast 的异常。
# (二).static_cast
static_cast 可以将指针或引用转换成它的基类或者派生类,当像下面一样转换成毫不相关的类指针时,编译就会报错。
A* foo = new A(); | |
// error: static_cast from 'A *' to 'B *', which are not related by inheritance, is not allowed | |
B* bar = static_cast<B*>(foo); |
但当转换的类有继承关系时,static_cast 便会直接转换,可能会导致错误的转换,引起程序的错误,所以使用 static_cast 时,程序的安全性需要由程序员来保证。但也正是因为 static_cast 在运行时不做额外的检查,所以相比 dynamic_cast,static_cast 的效率更高。
Base* pDerived = new Derived(1, "xiaoming"); | |
int count = 0; | |
while (std::cin >> count) | |
{ | |
int count2 = 0; | |
unsigned long long startTime = getUSec(); | |
for (count2 = 0; count2 < count; ++count2) | |
{ | |
Derived* pDerived2 = (Derived*)(pDerived); | |
} | |
unsigned long long endTime = getUSec(); | |
std::cout << "显式转换,次数:" << count2 << ",耗时:" << endTime - startTime << std::endl; | |
unsigned long long startTime2 = getUSec(); | |
for (count2 = 0; count2 < count; ++count2) | |
{ | |
Derived* pDerived2 = static_cast<Derived*>(pDerived); | |
} | |
unsigned long long endTime2 = getUSec(); | |
std::cout << "static_cast,次数:" << count2 << ",耗时:" << endTime2 - startTime2 << std::endl; | |
unsigned long long startTime3 = getUSec(); | |
for (count2 = 0; count2 < count; ++count2) | |
{ | |
Derived* pDerived2 = dynamic_cast<Derived*>(pDerived); | |
} | |
unsigned long long endTime3 = getUSec(); | |
std::cout << "dynamic_cast,次数:" << count2 << ",耗时:" << endTime3 - startTime3 << std::endl; | |
} |
通过以上代码,大致得到从基类指针转换为派生类时的性能对比:
10000 | |
显式转换,次数:10000,耗时:40 | |
static_cast,次数:10000,耗时:39 | |
dynamic_cast,次数:10000,耗时:554 | |
100000 | |
显式转换,次数:100000,耗时:398 | |
static_cast,次数:100000,耗时:455 | |
dynamic_cast,次数:100000,耗时:4116 | |
10000000 | |
显式转换,次数:10000000,耗时:22841 | |
static_cast,次数:10000000,耗时:20263 | |
dynamic_cast,次数:10000000,耗时:176645 |
可以看到普通的显式转换和 static_cast 效率相当,但 dynamic_cast 效率就相对比较低了。
但当从派生类转换为基类时:
10000 | |
显式转换,次数:10000,耗时:40 | |
static_cast,次数:10000,耗时:40 | |
dynamic_cast,次数:10000,耗时:40 | |
100000 | |
显式转换,次数:100000,耗时:234 | |
static_cast,次数:100000,耗时:212 | |
dynamic_cast,次数:100000,耗时:206 | |
10000000 | |
显式转换,次数:10000000,耗时:22814 | |
static_cast,次数:10000000,耗时:18660 | |
dynamic_cast,次数:10000000,耗时:18444 |
三种方式效率就相当了。
###(三).reinterpret_cast
reintepret_cast 的作用和基础的显示类型转换效果基本一致。它可以将任意相关或者不相关的指针或引用相互转换,甚至可以将指针转换为整型,这个过程不会判断指针的合法性,因此和 static_cast 一样,使用时也需要程序员去保证指针的合法。
# (四).const_cast
const_cast 可以改变一个指针或引用是否 const 的性质,它可以将一个 const 的指针转变为非 const,也可以将一个非 const 指针转变为 const。
# 4. 总结
平时我们项目中常用的基本就是 dynamic_cast 和 static_cast,不过由于 dynamic_cast 在父类指针转换成子类时,会消耗不少性能去转换类的合法性,所以我们项目中不少 dynamic_cast 在优化中通过一些标志类型的虚函数保证类的合法性,然后使用 static_cast 转换从而提升性能。此篇主要参考了官网的《Type Conversions》,从中也了解到 dynamic_cast 是通过 RTTI 来确定类的特性,从而判断转换是否合法的,这个目前我还不是很了解,下次继续研究~