第 17 節

C++ Type Casting

0瀏覽次數0訪問次數--跳出率--平均停留

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 double to int.
  • Convert enum class to an integer.
  • Safely cast a base class pointer to a derived class pointer.
  • Temporarily remove the const restriction 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:

  1. static_cast: Used for standard conversions that are checked at compile time.
  2. dynamic_cast: Used for safe downcasting in class hierarchies with runtime type checking.
  3. const_cast: Used to add or remove const qualifiers.
  4. reinterpret_cast: Used for low-level reinterpretation of bit patterns.
conversion methodMain Usessafety 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 typesRuntime check, return nullptr on failure or throw exception
const_cast<T>(expr)Add or remove const / volatile delimitersExercise extreme restraint
reinterpret_cast<T>(expr)Binary reinterpretation at the lowest level, such as pointer and integer interconversionMost 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_cast yields nullptr upon failure; when used on references, it throws std::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?

RequirementRecommended Writing StyleExplanation
double to intstatic_cast<int>(x)Clarify that decimals may be lost.
enum class Convert to integerstatic_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 interfaceconst_cast<T*>(p)The premise is that legacy interfaces do not modify actual const objects.
Pointer and integer conversion, underlying byte explanationreinterpret_cast<T>(x)Very low-level, prioritized to avoid

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.

使用建议

  • 明确目标:在开始前确定您的具体需求,以便选择最合适的工具或教程。
  • 充分利用资源:参考官方文档、教程和博客,这些资料能帮助您快速上手并解决问题。
  • 实践应用:通过动手操作项目或编写代码来巩固学习成果,提升实际操作能力。
  • 问题解决:遇到困难时,查阅参考资料或寻求社区支持,逐步培养独立解决问题的能力。
  • 分享经验:完成项目后,可以撰写文章或博客分享心得,帮助其他学习者。

如果需要针对特定领域(如单片机、机器人或环境搭建)的进一步建议,请提供更多信息,我将为您细化内容。

  1. Normal conversions should prioritize using static_cast.
  2. For polymorphic downcasts, preferentially use dynamic_cast and check for failure cases.
  3. const_cast is only for legacy compatibility; don't use it to modify the actual const object.
  4. reinterpret_cast should only be placed in very low-level, clearly bounded code and encapsulated centrally.
  5. 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 / volatile constraints.
  • 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.

音乐页