C++ 中dynamic_cast使用详解和实战示例

0.核心结论

dynamic_cast运行时类型识别(RTTI) 的主要工具,用于在 继承体系中安全 的类型转换。
只有两种情况应该使用它:

  1. 基类指针 / 引用 → 派生类指针 / 引用(runtime 检查)
  2. 多态继承体系中做跨层转换

如果没有虚函数表(没有 virtual 函数),就不能用


1. dynamic_cast 什么时候能用?

必须满足:

  • 必须是 类类型
  • 必须处于 继承体系
  • 父类必须至少有一个 虚函数(一般是 virtual destructor)

示例:

cpp 复制代码
struct Base {
    virtual ~Base() {}
};

struct Derived : Base {};

只有这样 dynamic_cast 才能工作。


2. dynamic_cast 四种合法用法

2.1 基类指针 → 派生类指针(最常用)

cpp 复制代码
Base* b = new Derived;
Derived* d = dynamic_cast<Derived*>(b);
if (d) { /* 成功 */ }
  • 成功:返回有效指针
  • 失败:返回 nullptr(非常安全!)

SLAM 框架里常用于模块的统一接口管理,例如:

cpp 复制代码
Frontend* f = factory();
Tracking* t = dynamic_cast<Tracking*>(f);

2.2 基类引用 → 派生类引用(不安全,会抛异常)

cpp 复制代码
Base& b = someRef;
try {
    Derived& d = dynamic_cast<Derived&>(b);
} catch (std::bad_cast& e) {
    // 转换失败
}

失败则抛 std::bad_cast

一般不建议在性能敏感代码使用引用形式的 dynamic_cast。


2.3 多重继承下横向转换

cpp 复制代码
struct A { virtual ~A() {} };
struct B { virtual ~B() {} };
struct C : public A, public B {};

A* a = new C;
B* b = dynamic_cast<B*>(a);   // 允许!

用于一些复杂的组件接口系统。


*2.4 void 转为任意类型(但要有类型信息)**

cpp 复制代码
void* p = some_base_ptr; 
Base* b = dynamic_cast<Base*>(p); // 不行

// 必须先为 Base* 才能 cast

严格来说 void* 不能直接 dynamic_cast,必须从多态指针转换而来

通常用于 C 接口和 C++ 之间的桥接。


3. 和 static_cast 的核心区别

要点 dynamic_cast static_cast
安全性 运行时检查 不检查,容易出错
性能 慢,需要 RTTI
失败行为 返回 nullptr/抛异常 UB(未定义行为)
使用场景 不确定真实类型时 你能 100% 确定类型时

举例:

SLAM 中模块:Module* m 指向 Tracking*Mapping*LoopClosure* 等多种类型

你不能用 static_cast ------ 工程大了就会出 bug。


4. 工程实战中常见正确使用方式

4.1 统一模块基类 → 多个子模块之间的识别

cpp 复制代码
Module* mod = manager.getModule("mapping");
Mapping* m = dynamic_cast<Mapping*>(mod);
if (m) m->optimize();

用于组件化架构(类似 ORB-SLAM / 自研框架)。


4.2 事件总线 / 消息总线中识别消息类型

cpp 复制代码
Message* msg = receiver.pop();

if (auto* imu = dynamic_cast<IMUMessage*>(msg)) { ... }
else if (auto* lidar = dynamic_cast<LidarMessage*>(msg)) { ... }

比硬编码 type 字段更安全。


4.3 后端优化节点类型识别

例如继承自 Factor 的不同残差项:

cpp 复制代码
Factor* f = graph.getFactor(i);
if (auto* imu = dynamic_cast<IMUFactor*>(f)) { ... }
if (auto* pose = dynamic_cast<PoseFactor*>(f)) { ... }

4.4 点云处理管线中识别不同 Frame 类型

cpp 复制代码
FrameBase* f = loader.load("cloud.bin");

if (auto* kf = dynamic_cast<KeyFrame*>(f)) { ... }
else if (auto* lf = dynamic_cast<LocalFrame*>(f)) { ... }

5. 性能问题(非常关键)

dynamic_cast 会:

  • 检查指针真实类型(RTTI)
  • 多态继承下可能遍历 vtable
  • 多重继承下可能跳指针偏移

不是零成本操作。

在 SLAM / 优化环节中大量调用会造成性能下降。

常规 SLAM 工程做法:

每次都 dynamic_cast

不好。

** 用枚举 + 虚函数替代大部分 dynamic_cast**

例如:

cpp 复制代码
enum class FrameType { KeyFrame, LocalFrame };

struct FrameBase {
    virtual FrameType type() const = 0;
};

但:当继承非常复杂时,dynamic_cast 最终还是跑不掉。


6. 工程实践要点

给基类加虚析构

cpp 复制代码
struct Base { virtual ~Base() = default; };

尽量避免多重继承

(尤其不能设计出钻石继承)

不要在高频循环中使用 dynamic_cast

(如 SLAM tracking 循环)

用 dynamic_cast 做一次类型识别,之后缓存结果

(常用技巧)

cpp 复制代码
if (auto* m = dynamic_cast<Mapping*>(mod)) {
    mapping_module = m;  // 缓存
}

不要把业务逻辑写到 type 判断上

多态设计 + 虚函数 经常比 RTTI 好。


7. 常见错误

对没有 virtual 的类使用

cpp 复制代码
struct A {};
struct B : A {};
A* a = new B;
dynamic_cast<B*>(a); // UB

用 static_cast 代替 dynamic_cast

容易踩雷。

在模板/泛型代码中滥用 dynamic_cast


8. 实用总结

当不确定指针实际指向哪个派生类时,用 dynamic_cast。
当能确定类型时,用 static_cast。
基类必须有虚函数。
失败返回 nullptr。


9.综合示例:SLAM 系统中的模块管理与 dynamic_cast

目标场景

假设一个 SLAM 系统有多种模块:

  • Tracking(前端)
  • Mapping(后端)
  • LoopClosure(回环检测)
  • Viewer(可视化)

用一个 ModuleManager 来管理所有模块,

并通过 dynamic_cast 进行运行时类型识别,

以便在调度任务时执行不同逻辑。


1. 模块基类(必须有 virtual)

cpp 复制代码
class Module {
public:
    virtual ~Module() = default;

    virtual std::string name() const = 0;
    virtual void process() = 0;
};

2. 若干子模块

cpp 复制代码
class Tracking : public Module {
public:
    std::string name() const override { return "tracking"; }
    void process() override {
        // 前端跟踪逻辑
    }

    void trackFeatures() { /* 特征跟踪 */ }
};

class Mapping : public Module {
public:
    std::string name() const override { return "mapping"; }
    void process() override {
        // 后端优化逻辑
    }

    void optimize() { /* 后端优化 */ }
};

class LoopClosure : public Module {
public:
    std::string name() const override { return "loop"; }
    void process() override {
        // 回环检测逻辑
    }

    void detectLoop() { /* 回环逻辑 */ }
};

3. 模块工厂(按字符串创建对象)

cpp 复制代码
class ModuleFactory {
public:
    static std::unique_ptr<Module> create(const std::string& type) {
        if (type == "tracking") return std::make_unique<Tracking>();
        if (type == "mapping")  return std::make_unique<Mapping>();
        if (type == "loop")     return std::make_unique<LoopClosure>();
        return nullptr;
    }
};

4. 模块管理器

(这里会展示 dynamic_cast 的推荐使用方式)

cpp 复制代码
class ModuleManager {
public:
    void addModule(std::unique_ptr<Module> mod) {
        // 缓存 raw 指针用于 quick lookup
        modules_[mod->name()] = mod.get();

        // 缓存 unique_ptr 来管理生命周期
        owned_.push_back(std::move(mod));
    }

    Module* get(const std::string& name) {
        return modules_.count(name) ? modules_[name] : nullptr;
    }

    // 统一调度所有模块
    void runOnce() {
        for (auto& kv : modules_) {
            Module* mod = kv.second;
            dispatch(mod);
        }
    }

private:
    // 这里展示 dynamic_cast 的核心使用方式
    static void dispatch(Module* mod) {
        // 1. Tracking
        if (auto* m = dynamic_cast<Tracking*>(mod)) {
            m->trackFeatures();
            return;
        }

        // 2. Mapping
        if (auto* m = dynamic_cast<Mapping*>(mod)) {
            m->optimize();
            return;
        }

        // 3. Loop closure
        if (auto* m = dynamic_cast<LoopClosure*>(mod)) {
            m->detectLoop();
            return;
        }

        // fallback(基类逻辑)
        mod->process();
    }

private:
    std::unordered_map<std::string, Module*> modules_;
    std::vector<std::unique_ptr<Module>>     owned_;
};

5. 使用示例(

cpp 复制代码
int main() {
    ModuleManager mgr;

    mgr.addModule(ModuleFactory::create("tracking"));
    mgr.addModule(ModuleFactory::create("mapping"));
    mgr.addModule(ModuleFactory::create("loop"));

    // 执行一帧 SLAM 主循环
    mgr.runOnce();

    return 0;
}

相关推荐
冷徹 .2 小时前
Edu144 CD
c++·算法
CodeByV2 小时前
【C++】C++11:右值引用和移动语义
开发语言·c++
一水鉴天2 小时前
整体设计 全面梳理复盘 之37 元级自动化引擎三体项目(Designer/Master/Transformer)划分确定 + 自用规划工具(增强版)
开发语言·算法·transformer·公共逻辑
自学互联网3 小时前
python爬虫入门案例day05:Pexels
开发语言·爬虫·python
头发还没掉光光3 小时前
C/C++类型转换
c语言·开发语言·c++
馨谙3 小时前
RHEL 存储堆栈完全解析:从硬件到应用的存储管理指南
服务器·开发语言·php
爪哇部落算法小助手3 小时前
爪哇周赛 Round 1
c语言·c++·算法
二川bro3 小时前
第38节:WebGL 2.0与Three.js新特性
开发语言·javascript·webgl
MediaTea3 小时前
Python 第三方库:Markdown(将文本渲染为 HTML)
开发语言·前端·python·html