含自定义删除器时能否将NATS库移出类头文件?
问题描述
我有如下代码片段:
#include <nats/nats.h> class MyClass { public: // some functions here private: template<typename T, void (*DestroyFn)(T*)> decltype(DestroyFn) makeDeleter() { return [](T* ptr) { DestroyFn(ptr); }; } std::unique_ptr<natsOptions, std::function<void(natsOptions*)>> m_options{ nullptr, makeDeleter<natsOptions, natsOptions_Destroy>()}; std::unique_ptr<natsConnection, std::function<void(natsConnection*)>> m_connection{ nullptr, makeDeleter<natsConnection, natsConnection_Destroy>()}; std::unique_ptr<natsSubscription, std::function<void(natsSubscription*)>> m_subscription{ nullptr, makeDeleter<natsSubscription, natsSubscription_Destroy>()}; };
若无需自定义删除器,我本可轻松前向声明NATS库的所用类型,但在拥有调用库销毁函数(如natsOptions_Destroy)的makeDeleter函数模板删除器时,是否仍能做到不将第三方库包含在当前头文件中?
代码评审要求不在当前头文件中包含第三方库,但我认为当前头文件仅被两个.cpp文件包含,若nats.h变更仅会导致两个翻译单元重编译。
解决方案
完全可以做到不在头文件中引入nats.h,核心是把删除器的实现、模板实例化以及第三方库的依赖全部移到.cpp文件,头文件仅保留必要的前向声明和接口,具体步骤如下:
1. 头文件中仅保留前向声明与接口
在头文件里前向声明NATS的类型,不需要包含nats.h,只保留MyClass的公共接口和成员变量声明:
// MyClass.h #include <memory> #include <functional> // 前向声明NATS库的不完整类型 struct natsOptions; struct natsConnection; struct natsSubscription; class MyClass { public: MyClass(); ~MyClass(); // 其他公共成员函数声明 private: // 用类型别名简化删除器的写法 using OptionsDeleter = std::function<void(natsOptions*)>; using ConnectionDeleter = std::function<void(natsConnection*)>; using SubscriptionDeleter = std::function<void(natsSubscription*)>; std::unique_ptr<natsOptions, OptionsDeleter> m_options; std::unique_ptr<natsConnection, ConnectionDeleter> m_connection; std::unique_ptr<natsSubscription, SubscriptionDeleter> m_subscription; };
2. 在.cpp文件中实现删除器与初始化
把makeDeleter模板、销毁函数的引用、成员变量的初始化都放到.cpp文件中,这里才需要包含nats.h:
// MyClass.cpp #include "MyClass.h" #include <nats/nats.h> namespace { // 将makeDeleter放在匿名命名空间,避免外部可见 template<typename T, void (*DestroyFn)(T*)> decltype(DestroyFn) makeDeleter() { return [](T* ptr) { if (ptr != nullptr) { DestroyFn(ptr); } }; } } // namespace MyClass::MyClass() : m_options(nullptr, makeDeleter<natsOptions, natsOptions_Destroy>()), m_connection(nullptr, makeDeleter<natsConnection, natsConnection_Destroy>()), m_subscription(nullptr, makeDeleter<natsSubscription, natsSubscription_Destroy>()) {} MyClass::~MyClass() = default;
可行性说明
std::unique_ptr支持不完整类型:只要在删除器执行(即对象析构)时,类型T是完整的即可。而析构函数的实现放在.cpp文件中,此时已经包含了nats.h,类型是完整的,不会触发未定义行为。- 无捕获lambda可转换为函数指针:这里的lambda没有捕获任何外部变量,能隐式转换为对应签名的函数指针,
std::function可以正常存储和调用它。
关于依赖隔离的价值
你提到当前头文件仅被两个.cpp引用,变更nats.h的影响范围很小,这个判断是对的,但遵循评审要求移除头文件中的第三方依赖,仍有实际意义:
- 避免传递依赖:其他包含
MyClass.h的代码不会意外依赖nats.h的内容,降低耦合度。 - 未来扩展性:如果后续这个头文件被更多模块引用,依赖隔离能有效减少编译时间的波动。
- 接口清晰:头文件只暴露
MyClass的公共接口,隐藏第三方库的实现细节,符合封装原则。
内容的提问来源于stack exchange,提问作者David Hovsepyan
相关产品推荐
相关产品推荐

