0.核心结论
dynamic_cast 是 运行时类型识别(RTTI) 的主要工具,用于在 继承体系中 做 安全 的类型转换。
只有两种情况应该使用它:
- 基类指针 / 引用 → 派生类指针 / 引用(runtime 检查)
- 多态继承体系中做跨层转换
如果没有虚函数表(没有 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;
}