0.旧式转换
先看看旧式的强制类型转换(如,整数进行浮点除法时会把其中一个变量用旧式转换转为 double):
//type(expr) 函数形式的强制类型转换
int count1 = 10;
bool not_empty1 = bool(count1); //为true
//或者(type)expr C语言风格的强制类型转换
int count2 = 0;
bool not_empty2 = (bool)count2; //为false
根据所涉及的类型不同,旧式的强制类型转换分别具有与 const_cast、static_cast、reinterpret_cast 相似的行为。与命名的强制类型转换相比,旧式的强制类型转换出问题更不容易追踪。
除了旧式的转换,还有四种命名的强制类型转换:static_cast、const_cast、dynamic_cast 和 reinterpret_cast。
1.static_cast
任何具有明确定义的类型转换,只要不包含底层const(形如const char *ptr,表示指针所指的对象是一个常量),都可以使用 static_cast。(C++ Primer Plus上说,只有两者其中一方可以隐式转换为另一方(如向上转型),转换才是合法的,否则会出错)
//进行强制类型转换以便执行浮点数除法
int a = 1, b = 2;
double result = static_cast<double>(a) / b;
可以参照cpp在线手册的例子来理解他的应用场景:
#include <vector>
#include <iostream>
struct B {
int m = 0;
void hello() const {
std::cout << "Hello world, this is B!\n";
}
};
struct D : B {
void hello() const {
std::cout << "Hello world, this is D!\n";
}
};
enum class E { ONE = 1, TWO, THREE };
enum EU { ONE = 1, TWO, THREE };
int main()
{
// 1: 初始化转换
int n = static_cast<int>(3.14);
std::cout << "n = " << n << '\n';
std::vector<int> v = static_cast<std::vector<int>>(10);
std::cout << "v.size() = " << v.size() << '\n';
// 2: 静态向下转型
D d;
B& br = d; // 通过隐式转换向上转型
br.hello();
D& another_d = static_cast<D&>(br); // 向下转型
another_d.hello();
// 3: 左值到右值
std::vector<int> v2 = static_cast<std::vector<int>&&>(v);
std::cout << "after move, v.size() = " << v.size() << '\n';
// 4: 弃值表达式
static_cast<void>(v2.size());
// 5. 隐式转换的逆
void* nv = &n;
int* ni = static_cast<int*>(nv);
std::cout << "*ni = " << *ni << '\n';
// 6. 数组到指针后随向上转型
D a[10];
B* dp = static_cast<B*>(a);
// 7. 有作用域枚举到 int 或 float
E e = E::ONE;
int one = static_cast<int>(e);
std::cout << one << '\n';
// 8. int 到枚举,枚举到另一枚举
E e2 = static_cast<E>(one);
EU eu = static_cast<EU>(e2);
// 9. 指向成员指针向上转型
int D::*pm = &D::m;
std::cout << br.*static_cast<int B::*>(pm) << '\n';
// 10. void* 到任何类型
void* voidp = &e;
std::vector<int>* p = static_cast<std::vector<int>*>(voidp);
}
//输出
/*****************************
n = 3
v.size() = 10
Hello world, this is B!
Hello world, this is D!
after move, v.size() = 0
*ni = 3
1
0
******************************/
也可用于通过进行到指定类型的函数指针转换,来消除函数重载的歧义,这在Qt信号槽里也很常用(Qt新版本中开始用QOverload替换这种写法),当信号有重载时,我们可以这样写:
//void currentIndexChanged(int index)
//void currentIndexChanged(const QString &text)
QComboBox *comboBox=new QComboBox(this);
connect(comboBox,static_cast<void(QComboBox::*)(const QString &)>(&QComboBox::currentIndexChanged),
this,[=](const QString &text){});
2.const_cast
const_cast运算符用于改变对象的const或volatile特征。该运算符只能改变运算对象的底层const(形如const char *ptr,表示指针所指的对象是一个常量):
const char* ptr = "gong jian bo";
//可以转换,但是通过p写值是未定义的行为
char* p = const_cast<char*>(ptr);
int i = 3; // 不声明 i 为 const
const int& rci = i;
const_cast<int&>(rci) = 4; // OK:修改 i
如果对象本身不是一个常量 ,使用强制类型转换获得写权限是合法的行为。然而如果对象是一个常量,再使用const_cast执行写操作就会产生未定义的行为。
只有const_cast能修改表达式的常量属性,使用其他形式的命名强制类型转换改变表达式的常量属性都将引发编译器错误。同样的,也不能用const_cast改变表达式的类型(只能用来改变常量属性)。
常用于函数重载的上下文中,如《Effective C++:改善程序与设计的55个具体做法》一书就举例:
class TextBlock {
public:
//完整的逻辑写在const版本中
const char &operator[](std::size_t position) const{
//... ...
return text[position];
}
//非const版本通过const_cast对const版本转换
char& operator[](std::size_t position) {
return const_cast<char&>( //将const转除
static_cast<const TextBlock&>(*this)//为*this加上const
[position] //调用const []
);
}
private:
char* text;
};
3.reinterpret_cast
reinterpret_cast 通常为运算对象的位模式提供较低层次上的重新解释。与 static_cast 不同,但与 const_cast 类似,reinterpret_cast 表达式不会编译成任何 CPU 指令(除非在整数和指针间转换,或在指针表示依赖其类型的不明架构上)。它纯粹是一个编译时指令,指示编译器将表达式视为如同具有新类型类型一样处理。它不允许删除const;可以将指针类型转换为足以存储指针表示的整形,但不能将指针转换为更小的整形或浮点型;不能将函数指针转换为数据指针,反之亦然。
int* int_ptr;
//char_ptr所指的真实对象是一个int而非字符,
//如果把他当作普通的字符指针使用,可能在运行时发生错误
char* char_ptr = reinterpret_cast<char*>(int_ptr);
可以参照cpp在线手册的例子来理解他的应用场景:
#include <cstdint>
#include <cassert>
#include <iostream>
int f() { return 42; }
int main()
{
int i = 7;
// 指针到整数并转回
std::uintptr_t v1 = reinterpret_cast<std::uintptr_t>(&i); // static_cast 为错误
std::cout << "The value of &i is 0x" << std::hex << v1 << '\n';
int* p1 = reinterpret_cast<int*>(v1);
assert(p1 == &i);
// 到另一函数指针并转回
void(*fp1)() = reinterpret_cast<void(*)()>(f);
// fp1(); 未定义行为
int(*fp2)() = reinterpret_cast<int(*)()>(fp1);
std::cout << std::dec << fp2() << '\n'; // 安全
// 通过指针的类型别名化
char* p2 = reinterpret_cast<char*>(&i);
if(p2[0] == '\x7')
std::cout << "This system is little-endian\n";
else
std::cout << "This system is big-endian\n";
// 通过引用的类型别名化
reinterpret_cast<unsigned int&>(i) = 42;
std::cout << i << '\n';
[[maybe_unused]] const int &const_iref = i;
// int &iref = reinterpret_cast<int&>(const_iref); // 编译错误——不能去除 const
// 必须用 const_cast 代替:int &iref = const_cast<int&>(const_iref);
}
//输出
/************************
The value of &i is 0x7fff352c3580
42
This system is little-endian
42
************************/
4.dynamic_cast
dynamic_cast运算符是最常用的RTTI(运行阶段类型识别)组件,它不能回答<指针指向的是哪类对象>这样的问题,但能够回答<是否可以安全地将对象的地址赋值给特定类型的指针>这样的问题。该运算符用于将基类的指针或引用安全的转换成派生类的指针或引用(向下转型)。如果转换目标是指针类型且失败了,结果为0,即空指针;如果转换目标是引用类型且失败了,则抛出一个bad_cast异常。
可以参照cpp在线手册的例子来理解他的应用场景:
#include <iostream>
struct V {
virtual void f() {}; // 必须为多态以使用运行时检查的 dynamic_cast
};
struct A : virtual V {};
struct B : virtual V {
B(V* v, A* a) {
// 构造中转型(见后述 D 的构造函数中的调用)
dynamic_cast<B*>(v); // 良好定义:v 有类型 V*,B 的 V 基类,产生 B*
dynamic_cast<B*>(a); // 未定义行为:a 有类型 A*,A 非 B 的基类
}
};
struct D : A, B {
D() : B((A*)this, this) { }
};
struct Base {
virtual ~Base() {}
};
struct Derived: Base {
virtual void name() {}
};
int main()
{
D d; // 最终派生对象
A& a = d; // 向上转型,可以用 dynamic_cast,但不必须
D& new_d = dynamic_cast<D&>(a); // 向下转型
B& new_b = dynamic_cast<B&>(a); // 侧向转型
Base* b1 = new Base;
if(Derived* d = dynamic_cast<Derived*>(b1))
{
std::cout << "downcast from b1 to d successful\n";
d->name(); // 可以安全调用
}
Base* b2 = new Derived;
if(Derived* d = dynamic_cast<Derived*>(b2))
{
std::cout << "downcast from b2 to d successful\n";
d->name(); // 可以安全调用
}
delete b1;
delete b2;
}
//输出: downcast from b2 to d successful
dynamic_cast主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换。
在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的;
在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。
5.参考
书籍:《C++Primer》中文第五版
书籍:《C++Primer Plus》中文第六版