You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

含自定义删除器时能否将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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 06:46:28