C++11 强制类型转换
当我们将一个数据类型转换为另一种类型时,这个过程被称为类型转换。如果将这里的数据类型替换为对象类型的指针或引用,那么对象类型之间的转换,就又称为向上转型或者向下转型。
假设有基类和派生类两种对象类型的指针或引用,对于派生类可以使用向上转型将其转换为基类,同理,基类也可以使用向下转型转换为派生类。向上转型是隐式的可以直接进行,向下转型则需要显式转换。
向下转型常见于公有继承,因为它允许基类指针合法的指向派生类对象,并且派生类对象可以访问基类的公有和保护成员。
在没有涉及指针和引用的情况下,对象的转换在 C++ 中是不直接支持的,尤其是涉及基类和派生类之间的转换。
直接对象转换
- 向上转型(Upcasting):直接将派生类对象赋值给基类对象是不可能的,因为这会导致对象切片(object slicing)。在对象切片中,派生类对象的基类部分被复制到基类对象中,但派生类特有的数据和方法将会丢失。
- 向下转型(Downcasting):基类对象无法直接转换为派生类对象,因为基类对象没有派生类的额外数据和行为。尝试这样的转换是无意义的,也是不被允许的。
C++11中,强制类型转换分为如下四类,之后将详细展开。
关键字 | 说明 |
---|---|
static_cast |
用于良性转换,一般不会导致意外发生,风险很低。 |
dynamic_cast |
借助RTTI ,用于类型安全的向下转型(Downcasting)。 |
const_cast |
用于 const 与非 const、volatile 与非 volatile 之间的转换。 |
reinterpret_cast |
高度危险的转换,这种转换仅仅是对二进制位的重新解释,不会借助已有的转换规则对数据进行调整,但是可以实现最灵活的 C++ 类型转换。 |
static_cast
语法:
static_cast< new_type >(expression)
static_cast相当于传统的C语言里的强制转换,该运算符把expression转换为new_type类型,用来强迫隐式转换如non-const对象转为const对象,static_cast是一种编译时检查的类型转换运算符,通常用于非多态的转换,但没有RTTI(运行时类型检查)来保证转换的安全性。它主要有如下几种用法:
- 基本数据类型之间的转换
- 指针和引用之间的转换
- 类层次结构中的向上转型和向下转型(不安全,向下转型通常建议使用dynamic_cast)
- void 指针和其他指针类型之间的转换
- 等等
注意事项:
- 类型安全:
static_cast
会在编译时进行类型检查,但它不会像dynamic_cast
那样在运行时进行类型检查。因此,对于基类和派生类之间的向下转换,必须确保转换是安全的。 - 不支持交叉转换:
static_cast
不能用于不相关的类型之间的转换。例如,不能将一个int*
转换为一个float*
。不同类型的数据存储格式不一样,长度也不一样,用 A 类型的指针指向 B 类型的数据后,会按照 A 类型的方式来处理数据:如果是读取操作,可能会得到一堆没有意义的值;如果是写入操作,可能会使 B 类型的数据遭到破坏,当再次以 B 类型的方式读取数据时会得到一堆没有意义的值。 - 不处理多态类型:
static_cast
不能用于处理多态类型(即带有虚函数的类)之间的转换。如果需要在类层次结构中进行安全的向下转换,应使用dynamic_cast
。
常见用法
int i = 10;
float f = static_cast<float>(i);
double d = 3.14;
int j = static_cast<int>(d);
class Base {};
class Derived : public Base {};
// 向上转换是安全的,因为派生类对象总是包含基类对象,向上转换也是隐式转换
Derived* derivedPtr = new Derived();
Base* basePtr = static_cast<Base*>(derivedPtr); // 方式二:Base* basePtr = derivedPtr;
// 向下转换可能不安全,如果转换不安全,会导致未定义行为。
Base* basePtr = new Derived();
Derived* derivedPtr = static_cast<Derived*>(basePtr);
Base& baseRef = *basePtr;
Derived& derivedRef = static_cast<Derived&>(baseRef);
void* voidPtr = &i;
int* intPtr = static_cast<int*>(voidPtr);
dynamic_cast
语法:
dynamic_cast< new_type >(expression)
newType和expression必须同时是指针类型或者引用类型。dynamic_cast只能转换指针类型和引用类型,其它数据类型都不支持。
dynamic_cast是四种类型转换中,最特殊的一种,它支持运行时识别指针或引用。
dynamic_cast用于在类的继承层次之间进行类型转换,它既允许向上转型(Upcasting),也允许向下转型(Downcasting)。向上转型是无条件的,不会进行任何检测,所以都能成功;向下转型的前提必须是安全的,要借助 RTTI 进行检测,所有只有一部分能成功。
dynamic_cast在程序运行期间借助RTTI进行类型转换,这就要求基类必须包含虚函数
static_cast在编译期间完成类型转换,能够更加及时地发现错误
dynamic_cast
主要有如下几种用法:
- 向下转型:从基类指针或引用转换为派生类指针或引用
- 向上转型:从派生类指针或引用转换为基类指针或引用(不常用,因为可以隐式转换)
- 横向转型:从一个类层次结构中的指针或引用转换为另一个相关联的类层次结构中的指针或引用
注意事项:
- 类型安全:
dynamic_cast
会在运行时进行类型检查,确保转换的安全性。如果类型转换不安全,则返回nullptr
(对于指针)或抛出std::bad_cast
异常(对于引用)。 - 必须有虚函数:基类必须包含至少一个虚函数,以启用运行时类型信息(RTTI)。没有虚函数的类无法使用
dynamic_cast
。 - 性能开销:由于
dynamic_cast
在运行时进行类型检查,因此相比于static_cast
,会有一定的性能开销。
常见用法:
//向下转型
class Base {
public:
virtual ~Base() = default; // 必须有虚函数以启用 RTTI
};
class Derived : public Base {
public:
void derivedOnlyMethod() { std::cout << "Derived only method" << std::endl; }
};
Base* basePtr = new Derived();
Derived* derivedPtr = dynamic_cast<Derived*>(basePtr);
if (derivedPtr) {
derivedPtr->derivedOnlyMethod(); // 转换成功,调用 Derived 特有的方法
} else {
std::cout << "dynamic_cast failed" << std::endl; // 转换失败
}
//向上转型
Derived* derivedPtr = new Derived();
Base* basePtr = dynamic_cast<Base*>(derivedPtr); // 实际上不需要 dynamic_cast,可以隐式转换
//横向转型(横向转型用法比较广泛,接下来将给出不同场景下的横向转换)
class AnotherDerived : public Base {
public:
void anotherMethod() { std::cout << "Another derived method" << std::endl; }
};
Base* basePtr = new Derived();
AnotherDerived* anotherDerivedPtr = dynamic_cast<AnotherDerived*>(basePtr);
if (anotherDerivedPtr) {
anotherDerivedPtr->anotherMethod();
} else {
std::cout << "dynamic_cast failed" << std::endl;
}
//引用类型的转换
try {
Base& baseRef = *basePtr;
Derived& derivedRef = dynamic_cast<Derived&>(baseRef);
derivedRef.derivedOnlyMethod();
} catch (const std::bad_cast& e) {
std::cout << "dynamic_cast failed: " << e.what() << std::endl;
}
横向转型
直接转换
如下代码展示了在多重继承环境下,基类指针之间的直接横向转换。
#include <bits/stdc++.h>
using namespace std;
class base1 {
public:
virtual void fun() { cout << "base1" << endl; }
virtual ~base1() {};
};
class base2 {
public:
virtual void fun() { cout << "base2" << endl; }
virtual ~base2() {};
};
class derived : public base1, public base2 {
public:
virtual void fun() { cout << "derived" << endl; }
virtual ~derived() {};
};
int main() {
base1* ptr1 = new derived;
base2* ptr2 = dynamic_cast<base2*>(ptr1);
if (ptr2) {
ptr2->fun();
} else {
cout << "derived error" << endl;
}
return 0;
}
间接转换
如下代码展示了在继承层次结构中,通过基类进行派生类之间的间接转换。
#include <iostream>
#include <typeinfo>
using namespace std;
class Base {
public:
virtual ~Base() = default;
};
class Derived1 : public Base {
public:
void greet() { cout << "Hello from Derived1!" << endl; }
};
class Derived2 : public Base {
public:
void greet() { cout << "Hello from Derived2!" << endl; }
};
void process(Base* base) {
if (Derived1* d1 = dynamic_cast<Derived1*>(base)) {
cout << "Converted to Derived1" << endl;
Base* base_temp = dynamic_cast<Base*>(d1); // 向上转换
if (Derived2* d2 = dynamic_cast<Derived2*>(base_temp)) { // 向下转换
d2->greet();
} else {
cout << "Conversion to Derived2 failed" << endl;
}
} else if (Derived2* d2 = dynamic_cast<Derived2*>(base)) {
cout << "Converted to Derived2" << endl;
Base* base_temp = dynamic_cast<Base*>(d2); // 向上转换
if (Derived1* d1 = dynamic_cast<Derived1*>(base_temp)) { // 向下转换
d1->greet();
} else {
cout << "Conversion to Derived1 failed" << endl;
}
} else {
cout << "Unknown type" << endl;
}
}
int main() {
Base* base1 = new Derived1;
Base* base2 = new Derived2;
process(base1); // 尝试从 Derived1 转换到 Derived2
process(base2); // 尝试从 Derived2 转换到 Derived1
return 0;
}
Converted to Derived1
Conversion to Derived2 failed
Converted to Derived2
Conversion to Derived1 failed
const_cast
const_cast
是 C++ 中的一个类型转换操作符,用于在不改变对象的底层 const 属性的情况下移除或添加 const 属性。它主要用于以下两种情况:
- 移除 const 属性:允许对原本声明为 const 的对象进行修改。
- 添加 const 属性:允许对原本非 const 的对象进行只读操作。
const_cast
不仅用于移除或添加 const
属性,还可以用于移除或添加 volatile
属性。volatile
是一种类型修饰符,用于告诉编译器变量可能会在程序控制之外被改变,因此编译器不要对该变量进行优化。
语法:
const_cast< new_type >(expression)
其中 new_type
是你希望转换成的新类型,expression
是你希望转换的对象或变量。
在C++中,const_cast
的作用对象是指针或引用,而不是直接作用于基本数据类型(如int
)。也就是说,你不能直接使用const_cast
去掉一个const int
的const
限定符,但是你可以通过指针或引用间接地达到这个目的。
移除 const 属性
有时候你可能需要修改一个原本声明为 const 的对象,这时可以使用 const_cast
移除 const 属性。例如:
void modifyValue(const int* ptr) {
int* modifiablePtr = const_cast<int*>(ptr);
*modifiablePtr = 10;
}
int main() {
const int value = 5;
modifyValue(&value);
return 0;
}
在这个例子中,modifyValue
函数接收一个指向 const int 的指针,但通过 const_cast
将其转换为非 const 指针,从而允许修改其指向的值。
添加 const 属性
虽然很少用,但 const_cast
也可以用于将非 const 对象转换为 const 对象。例如:
void readValue(int* ptr) {
const int* constPtr = const_cast<const int*>(ptr);
// 这里可以使用 constPtr 进行只读操作
}
int main() {
int value = 5;
readValue(&value);
return 0;
}
注意事项
- 安全性:使用
const_cast
移除 const 属性进行修改时,要确保对象本身确实是可以修改的,否则可能导致未定义行为。 - 用途限制:
const_cast
只能用于 const 属性和 volatile 属性的转换,不能用于其他类型的转换。
一般情况下,不建议使用const_cast
,但是这某些情况下,需要这种类型转换,如下述情况:
- 与旧代码的兼容
有时候,旧的代码库中没有使用 const
,而新的代码库为了安全性引入了 const
。为了与旧代码兼容,可以使用 const_cast
。
// 假设你的旧函数库没有使用const
void legacyFunction(int* ptr);
// 而你希望这新的函数库中使用const,应该使用如下方式
void newFunction(const int* ptr) {
// 你确定 legacyFunction 不会修改 ptr 指向的内容
legacyFunction(const_cast<int*>(ptr));
}
- API 的特殊需求
有时候某些 API 可能不接受 const
参数,但你知道它不会修改数据:
void printValue(int* ptr); // 某 API 函数原型
void displayValue(const int* ptr) {
// 你确定 printValue 只是读取数据,不会修改 ptr 指向的内容
printValue(const_cast<int*>(ptr));
}
移除 volatile
属性
volatile
关键字用于声明一个变量可能被程序之外的因素(如硬件或并发线程)修改,因此编译器不能对其进行优化。有时你需要移除 volatile
属性以便在不考虑外部修改的情况下对变量进行操作。
void processValue(volatile int* ptr) {
int* nonVolatilePtr = const_cast<int*>(ptr);
// 现在可以对 nonVolatilePtr 进行不考虑外部修改的操作
*nonVolatilePtr = 20;
}
int main() {
volatile int value = 10;
processValue(&value);
return 0;
}
添加 volatile
属性
你也可以使用 const_cast
将一个普通变量转换为 volatile
,以确保编译器不对该变量进行优化。
void observeValue(int* ptr) {
volatile int* volatilePtr = const_cast<volatile int*>(ptr);
// 现在 volatilePtr 将不被编译器优化
}
int main() {
int value = 30;
observeValue(&value);
return 0;
}
答疑
为什么const_cast
不直接支持const int
直接使用const_cast
去除基本数据类型(如const int
)的const
属性在语法和逻辑上是没有意义的,因为基本数据类型本身不是指向其他对象的指针或引用。基本数据类型的const
属性一旦设定,它在整个生命周期内都是不可变的,不涉及通过指针或引用间接修改的场景。
同时在设计上的考虑,C++的设计哲学是提供强大的类型安全性和灵活性。为了避免滥用类型转换而导致未定义行为,const_cast
被设计为仅作用于指针和引用,这样可以明确地控制和理解const
属性的去除或添加的行为。
reinterpret_cast
reinterpret_cast
是 C++ 中的一种类型转换操作符,用于在几乎不相关的类型之间进行低级别的强制类型转换。它通常用于需要对数据位进行重新解释的场景,比如将指针类型转换为整数类型,或者将一个类类型转换为另一个无关的类类型。
语法:
reinterpret_cast< new_type >(expression)
其中 new_type
是目标类型,expression
是要转换的对象或指针。reinterpret_cast
主要用于指针类型、引用类型和指针与整数类型之间的转换,但是,你不能直接用reinterpret_cast
在基本数据类型(如int
、float
、double
等)之间进行转换。如果你需要在基本数据类型之间进行转换,可以使用static_cast
或C风格的强制转换。
使用场景
指针类型之间的转换
reinterpret_cast
可以用于在不同的指针类型之间进行转换,例如将指针类型转换为无关的指针类型。在下面这个例子中,将 A
类型的指针转换为 B
类型的指针,但需要注意,这种转换可能导致未定义行为。
struct A {
int x;
};
struct B {
int y;
};
int main() {
A a;
B* b = reinterpret_cast<B*>(&a);
return 0;
}
指针和整数之间的转换
reinterpret_cast
可以将指针类型转换为整数类型,反之亦然。
int main() {
int x = 10;
int* p = &x;
uintptr_t intValue = reinterpret_cast<uintptr_t>(p);
int* p2 = reinterpret_cast<int*>(intValue);
return 0;
}
在这个例子中,指针被转换为一个整数类型 uintptr_t
,然后又被转换回指针类型。
注意事项
- 安全性:
reinterpret_cast
可能会导致未定义行为,特别是在将一种类型的指针转换为另一种无关类型的指针时。除非你完全理解目标类型和源类型在内存中的布局,否则这种转换可能是不安全的。 - 可移植性:使用
reinterpret_cast
进行的转换通常是不移植的,因为它依赖于平台的实现细节。 - 应用场景限制:尽量避免在高层次的应用代码中使用
reinterpret_cast
,它主要适用于低级别的系统编程或需要与硬件打交道的场景。
示例
// 将 函数指针 转换为 void 指针,再将 void 指针转为 函数指针
void myFunction() {}
int main() {
void (*funcPtr)() = myFunction;
void* voidPtr = reinterpret_cast<void*>(funcPtr);
void (*funcPtr2)() = reinterpret_cast<void(*)()>(voidPtr);
funcPtr2();
return 0;
}
// 将类指针转换为字符指针以访问原始字节数据
struct MyClass {
int a;
float b;
};
int main() {
MyClass obj = {5, 3.14f};
char* bytePtr = reinterpret_cast<char*>(&obj);
for (size_t i = 0; i < sizeof(obj); ++i) {
cout << static_cast<int>(bytePtr[i]) << " ";
}
return 0;
}