需求

cpp impl ?

解决

impl 使用接口类有两种方式:

  • 接口类只是作为一个壳,需要什么都是直接调用原来的类来实现
  • 接口类是使用虚函数,实现类通过继承来实现

代理类

代理类非常简单,下面这个 WeightProxy 就是壳,具体的实现都在 Weight 里面。

#ifndef WEIGHTPROXY_H
#define WEIGHTPROXY_H
#include <string>
#include <memory>
class Weight;

class WeightProxy
{
public:
    WeightProxy();
    ~WeightProxy();
    std::string GetInfo();
    std::unique_ptr<Weight> m_proxy;
};

#endif // WEIGHTPROXY_H
#include "Weightproxy.h"
#include "Weight.h"
WeightProxy::WeightProxy()
    : m_proxy(new Weight())
{
}

WeightProxy::~WeightProxy() = default;

std::string WeightProxy::GetInfo() {
    return m_proxy->GetInfo();
}
#include <iostream>     // std::streambuf, std::cout
#include "Weightproxy.h"
int main () {
    WeightProxy w;
    std::cout << w.GetInfo() << std::endl;
    return 0;
}

有以下几个注意点:

  • 如果使用指针或者引用可以实现,就不要用Object,因为定义某类型的Object 需要类型的定义式,而前者只需要声明.
  • 尽量用声明替换定义,当你声明一个函数,并且他用到某个class时,不需要该class的定义,即使函数是 Object传递参数或者返回值. 注意了,变量只有是指针我们才能使用声明,但是函数却没有这个限制,即使是对象也可使用声明,本质是函数编译不依赖于实现,但是调用函数之前,Test定义式一定要存在,重要的目的是把这种include形式传递到客户调用函数的那个文件中,将类型定义和客户端依赖去除,说白了库的提供者一个类中会提供很多函数,因为库的提供者选择 class形式,那么对于客户端只有需要 知道Test的具体定义的才去包含Test头文件,减少不必要的依赖
  • 为声明式和定义式提供不同头文件.因为定义式里面包含的头文件的真实实现,客户端不应该自己手工class声明,而是库实现侧自己提供两种头文件一个是声明、一个是定义声明文件就是给客户端像include的形式使用声明,也就是声明文件的内容就仅仅是 class Test

虚函数继承

这种虚函数继承,一般会搭配工厂模式。缺点是有虚函数开销和二进制兼容的问题。

// 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;
}

总结

  • 使用IMPL方式来较少类之间的依赖,减少编译时间
  • 变量可以使用指针,一大推变量使用结构体,类可以使用一个托管类,大致这三类型来实现减少依赖。
  • 其实本质上来说,头文件之间就不应该有定义的依赖,所以java中统一使用了指针,实现cpp中才是真正包含所有具体定义,头文件是用来声明这个类长什么样子,实现cpp中用来实现这个类内部怎么实现的。

参考

c++ 类之间的依赖问题:impl、代理模式

C++ IMPL 模式解析(上)

C++学习 | C++ Implement的使用 | 消除 warning C4251 | 精简库接口