构造函数参数注入是实现可测试性与解耦的基础,依赖应通过接口或模板传入,优先用std::shared_ptr/std::unique_ptr管理生命周期,避免裸指针和硬编码实现;不可移动依赖改用引用并外置生命周期管理;禁用setter注入以防半初始化状态。

构造函数参数注入依赖对象的写法
直接在类的构造函数中把依赖对象作为参数传进来,而不是在类内部 new 出来。这是实现可测试性的最基础一步,也是解耦的关键动作。
常见错误是把 std::shared_ptr 或裸指针类型写死在成员变量声明里,但没暴露构造入口——结果单元测试时根本没法替换模拟对象。
- 依赖类型尽量用接口(抽象基类)或模板参数,避免绑定具体实现
- 构造函数参数建议用
std::shared_ptr或std::unique_ptr,避免裸指针生命周期失控 - 如果依赖对象体积小、无状态,也可以接受值传递(比如
Config结构体)
示例:
class DataProcessor {
public:
explicit DataProcessor(std::shared_ptr<database> db, std::shared_ptr<logger> logger)
: m_db(std::move(db)), m_logger(std::move(logger)) {}
private:
std::shared_ptr<database> m_db;
std::shared_ptr<logger> m_logger;
};</logger></database></logger></database>
测试时如何传入 mock 对象
用 Google Test + gMock 时,mock 类必须继承自被依赖的接口,并在测试中实例化后传给被测类构造函数。
容易踩的坑是忘记调用 ON_CALL 或 EXPECT_CALL,导致 mock 行为未定义,运行时崩溃或静默失败。
- mock 对象生命周期必须长于被测对象,否则构造后立即析构会引发 dangling pointer
- 若使用
std::make_shared<mockdatabase>()</mockdatabase>,确保MockDatabase的析构函数是virtual - 不要在测试中用
new MockDatabase后裸传指针——得包一层std::shared_ptr再传
测试片段:
TEST(DataProcessorTest, ProcessCallsDb) {
auto mock_db = std::make_shared<mockdatabase>();
auto mock_logger = std::make_shared<mocklogger>();
EXPECT_CALL(*mock_db, query("SELECT *")).WillOnce(Return("data"));
DataProcessor processor(mock_db, mock_logger);
processor.process(); // 触发依赖调用
}</mocklogger></mockdatabase>
不支持移动语义时的替代方案
有些依赖类型不可拷贝也不可移动(比如含 std::mutex 成员的类),此时不能用 std::shared_ptr 包装,也不能按值传递。
解决方案是改用引用参数 + 生命周期外置管理,但必须明确文档化“调用方保证依赖生命周期不短于本类”。
- 构造函数参数改为
Database&和Logger&,并加const修饰只读依赖 - 测试时用局部变量创建 mock 对象,确保其作用域覆盖整个测试用例
- 禁止在构造函数里做耗时操作或抛异常——因为引用传入后无法回滚初始化
例如:
class Service {
public:
Service(Database& db, const Logger& logger) : m_db(db), m_logger(logger) {}
private:
Database& m_db;
const Logger& m_logger;
};
为什么不用 setter 注入而坚持构造注入
setter 注入看似灵活,但在 C++ 中会引入“对象处于半初始化状态”的风险:构造完成但依赖未设,后续任意方法调用都可能触发空指针或未定义行为。
更实际的问题是:Google Test 的 SetUp 是在构造测试 fixture 后才调用,而被测对象通常在 fixture 构造函数里就初始化了——这时 setter 还没机会调用。
- 构造注入能强制依赖完备性,编译期就能发现漏传
- 避免在类内部做
if (!m_db) throw std::runtime_error(...)这类运行时检查 - 配合依赖注入容器(如 dice、dip)时,只有构造注入能被自动解析
真正麻烦的点在于:当一个类有七八个依赖时,构造函数参数列表会很长。这不是设计问题,而是职责过重的信号——该拆了。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











