binding特化必须定义在头文件中且被调用处包含,否则跨翻译单元不可见;特化需用using type=...声明具体类型,不能用函数或operator();依赖构造参数须显式提供,无自动装配;应加static_assert校验可构造性。

binding 特化必须写在头文件里,否则跨翻译单元不可见——这是编译期 DI 最常踩的坑,没有之一。
为什么 resolve<T>() 在另一个 .cpp 里调用就失败
不是函数没定义,是模板特化根本没被看到。C++ 模板特化不像普通函数,它不参与链接,只在当前翻译单元实例化时生效。
-
binding<ILogger>写在logger.cpp里 →main.cpp包含injector.h后调用resolve<ILogger>(),编译器查不到特化,退回到默认实现(通常是static_assert(false)) - 即使把特化放进
logger.h,若该头没被main.cpp直接或间接包含,结果一样 - 错误提示往往指向
resolve调用点,但真正问题出在缺失特化的声明位置,而非调用逻辑
binding<T> 特化里不能直接返回对象,得用 using type = ...
编译期 DI 不靠运行时工厂,而是靠类型别名让 resolve<T>() 知道“该造谁”。写错形式会导致 SFINAE 失效或构造失败。
- 正确:
template<> struct binding<ILogger> { using type = ConsoleLogger; }; - 错误:
template<> struct binding<ILogger> { static ConsoleLogger instance() { return {}; } };—— 这不会被resolve识别 - 错误:
template<> struct binding<ILogger> { constexpr auto operator()() { return ConsoleLogger{}; } };—— 编译器无法在编译期推导返回类型用于构造
构造函数参数必须显式提供,resolve<T>() 不会自动递归解析依赖
这不是 Spring 或 boost::di,没有“自动装配”概念。C++ 模板无法在不实例化的情况下反射构造函数签名,所以你得自己把依赖链串起来。
- 若
UserService构造需要std::shared_ptr<ILogger>和Database&,就不能只写resolve<UserService>() - 得手动组合:
UserService{resolve<ILogger>(), resolve<Database>()} - 或者封装辅助模板:
make_user_service<ConsoleLogger, PostgresDB>(),但它内部仍硬编码了参数顺序和类型 - 如果某个依赖构造本身还要参数(比如
Database(std::string host, int port)),这些值必须在调用点显式传入,容器不接管
static_assert 报错位置容易偏移,得靠 std::is_constructible_v 提前卡住
接口契约不满足时,错误常出现在 new T{...} 那一行,而不是 resolve<T>() 调用处。这会让定位变慢,尤其当 T 是深层依赖时。
立即学习“C++免费学习笔记(深入)”;
- 推荐在
resolve实现里加一层校验:static_assert(std::is_constructible_v<T, Args...>, "T cannot be constructed with given dependencies") - 避免用
requires std::is_constructible_v<T>做模板约束——错误堆栈会跳过你的注入层,直接报底层构造失败 - 若
ConsoleLogger忘记继承ILogger或虚函数签名不一致,std::is_constructible_v<ILogger, ConsoleLogger>会为 false,但真正报错点可能在inject_impl<UserService>::get()的构造语句里


















