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.
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>
Run/Observe Result: This is a header file example that can be placed at the top of a complete program.
Basic Syntax
目标类型 value = static_cast<目标类型>(表达式);
目标类型 value = dynamic_cast<目标类型>(表达式);
目标类型 value = const_cast<目标类型>(表达式);
目标类型 value = reinterpret_cast<目标类型>(表达式);
Run/Observation Results: This section explains the syntax format, focusing on the differences in writing the four conversions.
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.
Example 3: const_cast only alters the const qualifier
const_cast can only add or remove const / volatile, cannot convert int into double, nor convert between unrelated types.
#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
This example succeeded in being modified because the real original object value was not const; it was only being observed through const int&. If the original object were inherently const, then forcibly modifying it via const_cast would result in undefined behavior.
Example 4: Using reinterpret_cast for Low-Level Reinterpretation
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.
#include <cstdint>
#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 << "*again = " << *again << "\n";
std::cout << std::boolalpha;
std::cout << "same pointer = " << (p == again) << "\n";
// 返回 0 表示程序正常结束。
return 0;
}
Results:
*again = 42
same pointer = true
This example is solely for illustrating that "a pointer can be converted to an integer that can hold the pointer, and then converted back." Do not casually use the converted integer for address calculations, nor should you force an unrelated object to be interpreted as a different object type.
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 |
Why C-style casting is not recommended:
C-style casting is concise to write:
int n = (int)3.14;
Run/Observe results: This part converts 3.14 to 3, but when reading the code, it's not clear which type of conversion it belongs to.
It is recommended to rephrase it as:
int n = static_cast<int>(3.14);
Running/Observing Results: This section will also yield 3, but the conversion intent is more explicit: this is regular numerical conversion.
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
Run/Observation Results: This is an incorrect example. Continuing to use cat when the actual object type is mismatched will produce undefined behavior.
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; // 未定义行为
Running/Observing Results: This is an incorrect example. const_cast can trick the compiler, but it doesn’t turn a true constant into a safely mutable object.
Abuse of reinterpret_cast
double d = 3.14;
int* p = reinterpret_cast<int*>(&d); // 危险:把 double 对象当 int 对象访问
Running/Observing Results: This is an error example; when violating type alias rules or object representation assumptions, the behavior is unpredictable.
使用建议
- 明确目标:在开始前确定您的具体需求,以便选择最合适的工具或教程。
- 充分利用资源:参考官方文档、教程和博客,这些资料能帮助您快速上手并解决问题。
- 实践应用:通过动手操作项目或编写代码来巩固学习成果,提升实际操作能力。
- 问题解决:遇到困难时,查阅参考资料或寻求社区支持,逐步培养独立解决问题的能力。
- 分享经验:完成项目后,可以撰写文章或博客分享心得,帮助其他学习者。
如果需要针对特定领域(如单片机、机器人或环境搭建)的进一步建议,请提供更多信息,我将为您细化内容。
- 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.