C++ Type Casting
What problem does this section solve?
In C++, it's common to encounter situations where "one type is treated as another type". For example:
- Convert
doubletoint. - Convert
enum classto an integer. - Safely cast a base class pointer to a derived class pointer.
- Temporarily remove the
constrestriction to adapt to the old interface. - Converting between pointers, integers, and byte views in the underlying code.
The C-style strong cast writing (目标类型)表达式 can accomplish many things, but that's also the issue: it’s too "versatile." A person reading the code can hardly tell at a glance whether this conversion is a normal numeric cast, an inheritance hierarchy conversion, removing const, or a dangerous low-level reinterpretation.
C++ provides four more explicit type-casting operators, making conversion intent clear in the code.
隐式转换和显式转换
类型转换可以分为隐式转换和显式转换。
隐式转换
隐式转换是由编译器自动完成的转换,代码中不需要写出转换操作。例如:
int count = 10;
double result = count;
这里 count 是 int,而 result 是 double。编译器会自动把整数 10 转换成浮点数 10.0,这就是隐式转换。
隐式转换写起来方便,但有些转换可能造成数据丢失:
double price = 19.8;
int whole_price = price;
这段代码通常可以通过编译,但 whole_price 得到的是 19,小数部分会被丢弃。因为转换没有明确写出来,阅读代码时也容易忽略它。
显式转换
显式转换是程序员在代码中明确要求进行的转换。例如:
double price = 19.8;
int whole_price = static_cast<int>(price);
这里的 static_cast<int> 明确表示:程序员知道 price 会被转换成 int,也接受小数部分被丢弃。
两者的区别可以简单理解为:
- 隐式转换:编译器根据上下文自动转换。
- 显式转换:程序员在代码中明确写出要转换成什么类型。
隐式转换并不一定不安全,显式转换也不代表一定安全。显式写出转换的主要价值,是让转换意图更清楚,并让编译器按照指定的转换方式进行检查。
下面介绍的 static_cast、dynamic_cast、const_cast 和 reinterpret_cast,都是 C++ 提供的显式类型转换。
What is this feature?
The four explicit type conversions in C++ are:
- static_cast: Used for standard conversions that are checked at compile time.
- dynamic_cast: Used for safe downcasting in class hierarchies with runtime type checking.
- const_cast: Used to add or remove const qualifiers.
- reinterpret_cast: Used for low-level reinterpretation of bit patterns.
| conversion method | Main Uses | safety level |
|---|---|---|
static_cast<T>(expr) | Common type conversions, such as numeric, enum, and parent-child class upcasting. | Commonly used, compile-time checking |
dynamic_cast<T>(expr) | Safe downcasting of polymorphic types | Runtime check, return nullptr on failure or throw exception |
const_cast<T>(expr) | Add or remove const / volatile delimiters | Exercise extreme restraint |
reinterpret_cast<T>(expr) | Binary reinterpretation at the lowest level, such as pointer and integer interconversion | Most dangerous, use sparingly. |
Principle: Avoid type casting whenever possible; when casting is necessary, always prefer the most semantically narrow conversion that best conveys the intended purpose.
C++ standard version
C++98 has provided these four types of casting since then, and in practical engineering, they are still recommended over C-style casts.
Required header files
Type conversion itself does not require additional header files. However, the libraries used in example code need their corresponding headers, such as:
#include <cstdint>
#include <iostream>
运行结果: 此代码块不含 main 函数,是语法、接口或分文件示例,不能独立运行,因此没有终端输出。
Basic Syntax
目标类型 value = static_cast<目标类型>(表达式);
目标类型 value = dynamic_cast<目标类型>(表达式);
目标类型 value = const_cast<目标类型>(表达式);
目标类型 value = reinterpret_cast<目标类型>(表达式);
运行结果: 此代码块不含 main 函数,是语法、接口或分文件示例,不能独立运行,因此没有终端输出。
Example code
Example 1: Using static_cast to Handle Routine Conversions
static_cast is suitable for explicit, routine conversions that can be checked at compile time. For example, numerical conversions, converting enum class to integers, and casting a base class pointer to point to a derived class object.
#include <iostream>
enum class Status
{
ok = 200,
not_found = 404
};
int main()
{
// 程序从 main 函数开始执行,下面的语句会按顺序运行。
double score = 89.7;
int integer_score = static_cast<int>(score);
Status status = Status::not_found;
int status_code = static_cast<int>(status);
std::cout << "integer_score = " << integer_score << "\n";
std::cout << "status_code = " << status_code << "\n";
// 返回 0 表示程序正常结束。
return 0;
}
Results:
integer_score = 89
status_code = 404
Example 2: Safe Check of Actual Object Type with dynamic_cast
dynamic_cast is commonly used for polymorphic base classes. During downcasting, it checks the object's actual type at runtime.
#include <iostream>
struct Animal
{
virtual ~Animal() = default;
};
struct Cat : Animal
{
void meow() const
{
std::cout << "cat: meow\n";
}
};
struct Dog : Animal
{
};
void try_meow(Animal* animal)
{
Cat* cat = dynamic_cast<Cat*>(animal);
if (cat != nullptr)
{
cat->meow();
}
else
{
std::cout << "not a cat\n";
}
}
int main()
{
// 程序从 main 函数开始执行,下面的语句会按顺序运行。
Cat cat;
Dog dog;
try_meow(&cat);
try_meow(&dog);
// 返回 0 表示程序正常结束。
return 0;
}
Results:
cat: meow
not a cat
Note: When used on pointers,
dynamic_castyieldsnullptrupon failure; when used on references, it throwsstd::bad_cast. It requires the base class to have at least one virtual function, typically a virtual destructor.
示例 3:const_cast 只改变 const 限定
const_cast 只能添加或移除 const / volatile 限定,不能把 int 转成 double,也不能把两个不相关的类型互相转换。
#include <iostream>
int main()
{
// 程序从 main 函数开始执行,下面的语句会按顺序运行。
int value = 10;
const int& readonly_ref = value;
int& writable_ref = const_cast<int&>(readonly_ref);
writable_ref = 20;
std::cout << "value = " << value << "\n";
// 返回 0 表示程序正常结束。
return 0;
}
Results:
value = 20
这个例子里,value 本身是一个普通的 int 变量,并不是 const int。
int value = 10;
const int& readonly_ref = value;
这两行代码的意思是:readonly_ref 是 value 的一个“只读引用”。它并没有创建一个新的变量,而是给 value 起了一个只能读取、不能修改的别名。
In other words:
readonly_ref
本质上还是指向原来的 value。
接下来:
int& writable_ref = const_cast<int&>(readonly_ref);
这行代码使用 const_cast 去掉了 readonly_ref 上的 const 限定,让它重新变成一个可以修改的引用。由于它引用的还是原来的 value,所以:
writable_ref = 20;
本质上就是在修改:
value = 20;
因此最后输出的是:
value = 20
需要注意的是,这个例子能够修改成功,是因为原始对象 value 本身不是 const,只是我们临时用 const int& 这种只读方式去引用它。
如果原始对象本身就是 const,再用 const_cast 强行修改就是未定义行为,例如:
const int value = 10;
const int& readonly_ref = value;
int& writable_ref = const_cast<int&>(readonly_ref);
writable_ref = 20; // 未定义行为
所以可以简单记住:
const_cast 只是把“只读视角”改回“可写视角”,它不会复制出新变量,也不会真的把一个天生的常量安全地变成普通变量。
示例 4:reinterpret_cast 做底层重解释
reinterpret_cast means "interpreting this binary data as a different type." It bypasses many type system protections and should generally only appear in very low-level code.
比如把一个指针转换成整数形式的地址,再把这个整数地址转换回指针。
reinterpret_cast 可以用来做一些比较“底层”的类型转换,比如把一个指针转换成整数形式的地址,再把这个整数地址转换回指针。
需要注意的是,这里转换的是地址,不是变量里面保存的值。
#include <cstdint>
#include <iomanip>
#include <iostream>
int main()
{
// 程序从 main 函数开始执行,下面的语句会按顺序运行。
int value = 42;
int* p = &value;
std::uintptr_t raw = reinterpret_cast<std::uintptr_t>(p);
int* again = reinterpret_cast<int*>(raw);
std::cout << "value = " << value << "\n";
std::cout << "p = " << p << "\n";
std::cout << "raw = " << raw << "\n";
std::cout << "raw(hex) = 0x" << std::hex << raw << std::dec << "\n";
std::cout << "again = " << again << "\n";
std::cout << "*again = " << *again << "\n";
std::cout << std::boolalpha;
std::cout << "same pointer = " << (p == again) << "\n";
// 返回 0 表示程序正常结束。
return 0;
}
运行结果示例:
value = 42
p = 0x7ffd60a576d4
raw = 140726224910036
raw(hex) = 0x7ffd60a576d4
again = 0x7ffd60a576d4
*again = 42
same pointer = true
其中,具体的地址值每次运行都可能不同,所以你的运行结果不一定和上面完全一样。
这段代码里:
int value = 42;
int* p = &value;
value 是一个普通的 int 变量,里面保存的值是 42。
p 是一个 int* 指针,里面保存的是 value 的地址,而不是 value 的值。
也就是说,p 里面存的不是:
42
而是类似这样的地址:
0x7ffd60a576d4
接着:
std::uintptr_t raw = reinterpret_cast<std::uintptr_t>(p);
这行代码把指针 p 转换成了一个整数。
std::uintptr_t 是一种无符号整数类型,它的作用是尽可能安全地保存一个指针地址。
所以如果:
p = 0x7ffd60a576d4
那么 raw 保存的就是这个地址对应的整数形式。
For example:
raw = 140726224910036
这只是同一个地址的十进制写法。
下面这行代码:
std::cout << "raw(hex) = 0x" << std::hex << raw << std::dec << "\n";
是把 raw 按十六进制打印出来。这样就可以看到,raw 的十六进制形式和指针 p 打印出来的地址基本是一样的。
然后:
int* again = reinterpret_cast<int*>(raw);
这行代码又把整数形式的地址 raw 转回了 int* 指针。
In other words:
p 指向 value
again 也指向 value
So:
std::cout << "*again = " << *again << "\n";
这里的 *again 就是在访问 again 指向的变量。
因为 again 重新指向了原来的 value,所以:
*again
Equivalent to:
value
因此输出结果是:
*again = 42
最后:
std::cout << "same pointer = " << (p == again) << "\n";
这行代码比较的是 p 和 again 这两个指针保存的地址是否相同。
由于它们都指向 value,所以结果是:
same pointer = true
可以把整个过程理解成:
value 是一个变量,值是 42
p 保存 value 的地址
raw 把这个地址用整数形式保存下来
again 又把这个整数地址转换回指针
所以 again 重新指向 value
所以这段代码的重点不是把 42 转成整数,而是把 value 的地址转成整数。
简单记忆:
int* p = &value;
Indicates:
拿到 value 的地址
std::uintptr_t raw = reinterpret_cast<std::uintptr_t>(p);
Indicates:
把地址当成整数保存
int* again = reinterpret_cast<int*>(raw);
Indicates:
把整数地址再变回指针
*again
Indicates:
通过这个地址找到原来的 value
一句话总结:
reinterpret_cast 在这个例子里转换的是“地址的表示形式”,不是变量里面的值。value 的值是 42,而 raw 保存的是 value 的地址。
How to choose among the four types of conversions?
| Requirement | Recommended Writing Style | Explanation |
|---|---|---|
double to int | static_cast<int>(x) | Clarify that decimals may be lost. |
enum class Convert to integer | static_cast<int>(e) | Enum classes have no implicit conversion to integers. |
| Base class pointer to derived class pointer, and the actual type is uncertain. | dynamic_cast<Derived*>(p) | Conversion failure can be determined by nullptr |
Remove const to adapt to the old interface | const_cast<T*>(p) | The premise is that legacy interfaces do not modify actual const objects. |
| Pointer and integer conversion, underlying byte explanation | reinterpret_cast<T>(x) | Very low-level, prioritized to avoid |
类型转换 对比 C风格强转 会增加运行开销吗
static_cast<T>(expr) 中虽然使用了尖括号,看起来有些像模板,但它不是模板,而是 C++ 语言内置的类型转换运算符。这里的 T 表示要转换成的目标类型。
与功能相同的 C 风格强转相比,C++ 的显式类型转换通常不会产生额外的运行开销:
|conversion method|通常的运行开销|
|:---|:---|
|static_cast|不会因为使用这种写法而增加额外开销|
|dynamic_cast|需要在运行时检查对象的真实类型,可能产生时间和程序体积开销|
|const_cast|通常只改变编译器进行类型检查的方式,没有运行时操作|
|reinterpret_cast|通常只改变编译器解释数据的方式,没有额外转换步骤|
例如,下面两种写法表达的是同一种数值转换:
double value = 3.14;
int a = (int)value;
int b = static_cast<int>(value);
编译器通常会为它们生成相同或等价的机器指令。static_cast 的优势不是运行得更快,而是转换意图更加明确,并且能够得到更严格的编译期检查。
不过,需要区分“转换写法的开销”和“转换操作本身的开销”。例如,把浮点数转换成整数可能需要执行实际的处理器指令:
double value = 3.14;
int result = static_cast<int>(value);
这里可能产生的运行开销来自“把 double 转换成 int”这个操作,而不是来自 static_cast 语法。即使改成隐式转换或 C 风格强转,这个转换操作仍然存在。
因此可以简单记住:选择 C++ 的显式类型转换,主要是为了让类型检查更严格、代码意图更清楚;除了需要运行时检查的 dynamic_cast,它通常不会因为写法不同而比 C 风格强转更慢。
Why C-style casting is not recommended:
C-style casting is concise to write:
int n = (int)3.14;
运行结果: 此代码块不含 main 函数,是语法、接口或分文件示例,不能独立运行,因此没有终端输出。
It is recommended to rephrase it as:
int n = static_cast<int>(3.14);
运行结果: 此代码块不含 main 函数,是语法、接口或分文件示例,不能独立运行,因此没有终端输出。
C-style casting might implicitly combine multiple conversion capabilities behind the scenes, behaving both like static_cast and like const_cast, and sometimes even approaching reinterpret_cast. The more low-level the code and the more complex the types, the greater the risk of this "undocumented" behavior.
Common Errors
Perform unsafe downcasting with static_cast
Downcasting involves converting a base class pointer or reference to a derived class type. When using static_cast for this purpose, it is considered unsafe because it bypasses runtime type checking. This means the cast is performed at compile time without verifying whether the object is actually of the target derived type. If the conversion is incorrect, static_cast will not detect the error, potentially leading to undefined behavior, such as accessing invalid memory or invoking incorrect functions.
For safe downcasting in polymorphic hierarchies (where base classes have virtual functions), dynamic_cast is recommended. It performs runtime type information (RTTI) checks and returns nullptr (for pointers) or throws std::bad_cast (for references) if the cast is invalid.
Example of an unsafe downcast with static_cast:
class Base {
public:
virtual void foo() {} // Makes the class polymorphic
};
class Derived : public Base {
public:
void bar() { /* Derived-specific logic */ }
};
Base* basePtr = new Derived(); // Valid: base pointer to derived object
Derived* derivedPtr = static_cast<Derived*>(basePtr); // Unsafe downcast
derivedPtr->bar(); // Works here, but risky if basePtr actually points to a Base object
Base* basePtr2 = new Base();
Derived* derivedPtr2 = static_cast<Derived*>(basePtr2); // Unsafe and dangerous
derivedPtr2->bar(); // Undefined behavior: basePtr2 does not point to a Derived object
Animal* animal = new Dog;
Cat* cat = static_cast<Cat*>(animal); // 危险:编译可能通过,但真实对象不是 Cat
运行结果: 此代码块不含 main 函数,是语法、接口或分文件示例,不能独立运行,因此没有终端输出。
If the actual type is unknown, dynamic_cast should be used.
Modify True Const Objects
const int value = 10;
int& ref = const_cast<int&>(value);
ref = 20; // 未定义行为
运行结果: 此代码块不含 main 函数,是语法、接口或分文件示例,不能独立运行,因此没有终端输出。
Abuse of reinterpret_cast
double d = 3.14;
int* p = reinterpret_cast<int*>(&d); // 危险:把 double 对象当 int 对象访问
运行结果: 此代码块不含 main 函数,是语法、接口或分文件示例,不能独立运行,因此没有终端输出。
使用建议
- 明确目标:在开始前确定您的具体需求,以便选择最合适的工具或教程。
- 充分利用资源:参考官方文档、教程和博客,这些资料能帮助您快速上手并解决问题。
- 实践应用:通过动手操作项目或编写代码来巩固学习成果,提升实际操作能力。
- 问题解决:遇到困难时,查阅参考资料或寻求社区支持,逐步培养独立解决问题的能力。
- 分享经验:完成项目后,可以撰写文章或博客分享心得,帮助其他学习者。
如果需要针对特定领域(如单片机、机器人或环境搭建)的进一步建议,请提供更多信息,我将为您细化内容。
- Normal conversions should prioritize using
static_cast. - For polymorphic downcasts, preferentially use
dynamic_castand check for failure cases. const_castis only for legacy compatibility; don't use it to modify the actualconstobject.reinterpret_castshould only be placed in very low-level, clearly bounded code and encapsulated centrally.- Avoid using C-style casts just to save a few keystrokes.
Summary
static_cast: Regular conversion, most commonly used.dynamic_cast: Polymorphic type-safe downcast.- Only change
const/volatileconstraints. reinterpret_cast: Reinterpreted at the lowest level, carrying the highest risk.
One-sentence memory: Don't use it if you can; when necessary, clearly state the conversion intent.