自己写DI容器易翻车,因多数场景std::unique_ptr+工厂函数已足够;真正需自研仅嵌入式无RTTI/异常或需统一接管旧模块构造逻辑两种情况。

为什么不用第三方库,自己写 DI 容器反而容易翻车
自己实现 C++ DI 容器不是不行,但绝大多数场景下,std::unique_ptr + 手动构造 + 工厂函数已经够用;硬上“容器”往往是因为对依赖生命周期、线程安全或配置灵活性有误判。真正需要自研 DI 的典型场景只有两个:一是嵌入式环境禁用 RTTI/异常/动态内存(此时连 std::any 都不能用),二是已有复杂模块需统一接管构造逻辑且无法改接口。
最简可行版:用 std::unordered_map + std::function 绑定工厂
核心是避免模板爆炸和类型擦除开销,先支持单例注入。关键不是“注册”,而是“延迟创建+类型安全获取”:
-
register_type<t>()</t>实际注册的是std::function<:shared_ptr>()></:shared_ptr>,返回void*再static_cast回去——但这样丢掉类型检查,不推荐 - 更稳妥的做法是每个类型注册独立的
std::function<:shared_ptr>()></:shared_ptr>,用typeid(T).hash_code()当 key 存进std::unordered_map<size_t std::shared_ptr>></size_t>,get 时直接 cast 指针 - 注意:如果 T 含虚函数,
static_cast<:shared_ptr>*>(ptr)</:shared_ptr>会失败,必须用dynamic_pointer_cast,但这就要求开启 RTTI - 示例注册:
container.register_instance<myservice>(std::make_shared<myservice>(dep1, dep2));</myservice></myservice>—— 这比运行时工厂更可控,也更符合 C++ 零成本抽象原则
std::shared_ptr 生命周期管理的三个坑
DI 容器最常崩在对象析构顺序和循环引用上,不是语法问题,是语义误用:
- 把
std::shared_ptr<a></a>和std::shared_ptr<b></b>互相持有对方,即使容器本身没循环注册,实例间也会 leak——std::weak_ptr必须由业务代码显式插入,容器不替你决策 - 单例模式下,若用
std::shared_ptr管理全局对象,而某个模块提前调用reset(),后续 get 就返回空指针,且无编译提示 - 跨 DLL 边界传递
std::shared_ptr时,若两边 CRT 不同(如 MSVC 的 /MD vs /MT),shared_ptr的控制块析构会 crash——此时只能传裸指针 + 明确所有权约定
什么时候该停手:别让 DI 容器变成新的耦合中心
当你开始给容器加 YAML 配置解析、作用域嵌套(request-scoped)、AOP 拦截或自动装配(auto-wiring)时,就说明它正在取代编译器做类型检查。C++ 的头文件即契约,过度依赖运行时注册会让 #include 失去意义,重构 rename 一个类名,要 grep 全局所有字符串字面量里的类名。
立即学习“C++免费学习笔记(深入)”;
真正轻量的解耦,靠的是接口抽象 + 构造函数参数化,而不是把 new 搬进容器里。容器只该解决“谁来 new”和“new 几次”,不该决定“怎么 new”。



















