C++ IMPL 模式解析(上)

抛砖引玉

试想一个问题,如果有一套收发数据的网络接口,需要提供给其他同事或厂家使用,包含头文件和动态库,假设头文件如下:

// 版本1
class NetworkV1 {
public:
    int Send(const std::string str);
    int Recv(std::string &str);
private:
    int sockfd;
    char buf[1024];
};

用户直接 include 头文件,链接库文件即可。方法上没有问题,但问题是头文件中暴露的信息太多了,比如 private 成员变量,而且如果以后的版本中需要增加或删除某些变量,还需要通知用户修改头文件,太麻烦了。

为了解决这个问题,实现接口与实现分离,所以引入了 IMPL 模式。


C++ IMPL 模式

这里的 IMPL 其实就是 implement,即实现的意思,个人觉得 IMPL 严格上来讲,并不算一个设计模式,只是一个更好的 隐藏实现的方法 。已 C++ 为例,它不仅仅是将类的声明和实现放在不同的文件中,更重要的是隐藏细节,只暴露用户必须的接口部分。先看一版改进的代码:

// network.h
// 版本2
class NetworkV2 {
public:
    int Send(const std::string str);
    int Recv(std::string &str);

private:
    struct Impl;
    std::shared_ptr<Impl> impl;
};


// network.cpp
// 版本2
struct NetworkV2::Impl {
    int sockfd;
    char buf[1024];
};

int NetworkV2::Send(const std::string str) {
    // TODO ...
    // send(impl->sockfd, str.c_str(), str.size(), 0);
    return str.size();
}
int NetworkV2::Recv(std::string &str) {
    // TODO ...
    // recv(impl->sockfd, impl->buf, 1024, 0);
    return str.size();
}

这样就做到了隐藏类中的成员变量了,核心思想就是 将成员变量打包放在一个结构体中 ,无论以后的版本中有无删减成员变量,都不会对头文件造成任何影响,这是目前 C++ IMPL 中非常常见的一种调用方法。类似于 C 语言中的 void* 指针,可以在需要的时候转换成任意对象。


完全隐藏成员变量

但是上一种模式还是会有 private 的成员变量,如果是想要完全隐藏,只保留接口呢?我在学习上交所 CTP 接口的时候,还看见过一种新的方法,核心思想是 使用虚函数和继承 。这种模式头文件中只保留接口,不会有任何的成员变量,代码如下:

// network.h
// 版本3
class NetworkV3 {
public:
    virtual int Send(const std::string str) = 0;
    virtual int Recv(std::string &str) = 0;

    // 创建和销毁函数
    static NetworkV3* New();
    static void Delete(NetworkV3 *net);
};

// network.cpp
// 版本3
class NetworkV3Impl final : public NetworkV3 {
public:
    int Send(const std::string str) override {
        std::cout << "NetworkV3Impl::Send: " << str << std::endl;
        return str.size();
    }

    int Recv(std::string &str) {
        str = "ok";
        std::cout << "NetworkV3Impl::Recv: " << str << std::endl;
        return str.size();
    }
};

// 创建和销毁函数
NetworkV3* NetworkV3::New() {
    return (new NetworkV3Impl());
}
void NetworkV3::Delete(NetworkV3 *net) {
    delete (NetworkV3Impl*)net;
}

虽然消除了 private 成员变量,但增加了两个静态成员函数 NewDelete ,用于创建和销毁对象,也不需要用户自己管理内存,使用上很方便,像 CTP 的 C++ 接口就是采用的这种模式。但后来查资料,发现这种方法有两个主要的弊端:

  • 虚函数开销 :虚函数需要使用虚函数表指针间接调用,运行时才能确定调用哪一个函数,无法在编译期间内联优化。在上一版中,在编译期就能确定调用哪一个函数,根本用不到虚函数的特性。
  • 二进制兼容 :虚函数是按照索引查询虚函数表来调用的,新增或调整虚函数顺序会造成索引变化,导致新接口在二进制层面不能兼容老接口,就是在末尾增加虚函数,也会有风险。
  • 12
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值