C++中构造函数与模板参数依赖注入的权衡对比
何时使用构造函数实现依赖注入,何时使用模板参数实现依赖注入
示例
现有如下类定义:
Interface:
/// Interface class class Interface { public: virtual void Method() = 0; virtual ~Interface() = default; };
Concrete:
/// Concrete class class Concrete : public Interface { public: void Method() override { std::cout << "Concrete.Method()\n"; } static void StaticMethod() { std::cout << "Concrete.StaticMethod()\n"; } };
- 通过构造函数注入
Interface依赖的ConstructorDI:
/// Class with constructor dependency injection class ConstructorDI { public: explicit ConstructorDI(Interface &interface) : interface_(interface) { std::cout << "** ConstructorDI() **" << std::endl; // Call the Method() exposed by the interface interface_.Method(); } private: // Interface-based dependency (this class doesn't own the underlying concrete // type) Interface &interface_; };
- 基于模板参数注入依赖的
TemplateDI:
/// Class with template dependency injection template <typename T> class TemplateDI { public: TemplateDI() { std::cout << "** TemplateDI() **" << std::endl; // Call the Method() t_.Method(); // Call the StaticMethod() T::StaticMethod(); } private: // Type-based dependency (this class owns the concrete type) // Thus, cannot be a Interface (pure abstract) class T t_; };
典型使用方式如下:
// Concrete class implementing the Interface class Concrete concrete; // Create a class using dependency injection via constructor ConstructorDI constructor_d_i(concrete); // Create a class using dependency injection via template TemplateDI<Concrete> template_d_i;
运行输出:
** ConstructorDI() ** Concrete.Method() ** TemplateDI() ** Concrete.Method() Concrete.StaticMethod()
已观察到的特性
- 构造函数依赖注入可以依赖不由当前类持有所有权的具体类型
- 模板依赖注入允许类持有具体类型的所有权,同时支持调用依赖的静态方法
问题解答
1. C++还有其他依赖注入实现方式吗?
你提到的是最常用的两种,除此之外还有几种常见实现:
- Setter注入:不在构造阶段传入依赖,而是为类提供公开的setter方法,允许在类实例化之后再替换、设置依赖
- 函数参数注入:不将依赖存储为类成员,仅在需要调用依赖的成员函数执行时,将依赖作为参数传入
- 闭包注入:通过lambda、std::function等可调用对象封装依赖的调用逻辑,将可调用对象注入到目标类中
- 依赖注入容器:通过第三方通用框架实现依赖的自动注册、解析,无需手动在构造/模板参数中传递依赖
2. 二者在可测试性上是否存在显著差异?
二者的可测试性都非常好,差异仅在于适用的测试场景:
- 构造函数注入基于多态实现,测试时可以直接传入
Interface的Mock子类实例,不需要重新编译被测类,适合需要在运行时快速切换Mock实现的场景,调试成本更低 - 模板注入基于鸭子类型实现,测试时需要定义符合接口约定的Mock类型,作为模板参数传入被测类即可完成测试。如果被测模板类的实现和声明分离,会增加测试代码的编译成本,同时编译期的类型检查报错信息可读性更低。但模板注入支持对静态方法的Mock,这是构造函数注入做不到的。
3. 两种方式的适用场景分别是什么?
选择构造函数注入的场景:
- 依赖需要在运行时动态切换,比如根据配置切换不同的日志实现、数据存储实现
- 需要减少编译依赖、降低模块耦合:依赖的抽象接口可以单独放在头文件,具体实现放在cpp文件,修改实现时不需要重新编译所有依赖该类的代码
- 依赖的生命周期不由当前类管理,比如依赖是全局单例、其他模块持有的实例,当前类仅需要调用其能力不需要控制其销毁
选择模板注入的场景:
- 性能敏感场景:模板注入没有虚函数调用开销,编译期可以完成内联优化,适合高频调用的核心路径
- 需要调用依赖的静态方法,或者需要当前类持有依赖的完整所有权
- 依赖类型在编译期就可以完全确定,不需要运行时切换,比如算法的策略选择、通用工具类的定制化扩展
- 不想额外定义抽象虚接口的场景,只要依赖符合约定的方法签名就可以直接注入,减少冗余代码
内容的提问来源于stack exchange,提问作者Jan Gabriel
相关产品推荐
相关产品推荐

