构建可自动注册与解析的依赖注入容器需在编译期确定依赖、运行时零反射构造,通过container单例管理类型id映射、lambda注册、智能指针生命周期控制及di_register宏实现跨模块自动注册。

构建可自动注册与解析的依赖注入容器
需要让C++程序在编译期确定组件依赖关系、运行时零反射完成对象构造与注入,避免手写工厂或硬编码new表达式。这要求容器能识别模板参数类型、支持生命周期管理、允许跨模块注册且不依赖宏展开污染全局命名空间。
新建头文件 di_container.h,定义核心模板类 Container,它内部用 std::unordered_map 存储类型ID到构造函数指针的映射,类型ID由 typeid(T).hash_code() 生成——注意此处必须用 std::type_info::hash_code() 而非 name(),因为后者在不同编译单元可能返回不同字符串地址导致注册失效。
声明静态成员函数 instance() 返回单例引用,所有注册与解析操作均通过该实例进行。
实现类型擦除的注册接口
为支持任意构造参数的类型注册,定义模板函数 register_type:
方法一:接受一个lambda,该lambda接收 Container& 参数并返回目标类型的堆分配对象。例如注册无参类 ServiceA:c.register_type<servicea>([](auto& c) { return std::make_unique<servicea>(); });</servicea></servicea>
方法二:对有依赖的类(如 ServiceB 依赖 ServiceA&),lambda中调用 c.resolve<servicea>()</servicea> 获取实例,再传入构造函数——【必须确保被依赖类型已先注册,否则 resolve 抛出 std::runtime_error】。
方法三:支持作用域控制,在lambda内返回 std::shared_ptr 或 std::unique_ptr,容器根据指针类型自动判断是单例还是瞬态生命周期。
编写类型安全的解析逻辑
定义模板函数 resolve:
第一步:计算 typeid(T).hash_code() 得到键值;
第二步:在内部 map 中查找对应构造器;
第三步:若未找到,抛出带类型名的异常,使用 abi::__cxa_demangle 解析 typeid(T).name() 提高调试可读性;
第四步:调用存储的 lambda,传入当前容器引用,获取新对象;
第五步:将对象存入缓存(仅当返回 shared_ptr 且为首次解析时),然后返回解引用后的引用或移动后的独占指针。
启用跨编译单元自动注册
在 di_registry.h 中定义宏 DI_REGISTER(Type, Lambda),其本质是声明一个静态局部变量,利用其构造函数执行注册动作:
static auto registry_##Type = []{ Container::instance().register_type<type>(Lambda); }();</type>
每个模块只需在cpp文件顶部写一行 DI_REGISTER(Logger, [](auto& c) { return std::make_unique<filelogger>(); });</filelogger>,链接时该变量必被初始化,从而触发注册。
注意:此宏不能放在头文件中被多处包含,否则引发 ODR 违规;必须限定在单个 .cpp 文件内使用。
集成构建与验证
创建 CMakeLists.txt,设置 C++17 标准,添加 add_executable(di_demo main.cpp);
在 main.cpp 中包含 di_container.h 和各服务头文件,调用 Container::instance().resolve<serviceb>()</serviceb> 获取实例;
编译后运行,观察控制台输出是否正确打印依赖链初始化日志,确认 ServiceA 构造早于 ServiceB 且仅执行一次。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











