C++中的常用接口

C++中的常用接口

目的

某一模块开发完成后,需要提供一个对外接口,让模块代码与调用者代码可以独立变化而互不影响,同时也能隐藏模块的实现细节;

pImpl技法

当自己的模块进行升级时,若接口类中成员变量发生变化,那么调用者的代码就需要重新编译,这种情况应该极力避免,因此需要一种方法来消除模块之间的编译依赖,降低耦合性;

在C++中,对于上述问题,pImpl 惯用技法(pointer to implementation idiom)被广泛使用。其具体做法是:实现细节的类不对外开放,在接口中仅仅存放实现类的指针,以隔离接口与实现,当实现变化时,只要被调用的接口函数形式不变,调用者代码就不会受到影响,无需重新编译。示例代码如下所示:

接口头文件代码(只有此头文件提供给调用者,其他文件均不对外开放):

//  ----------------------------------------------------------------------------
/**
 * @file  Demo.h
 * @note  本接口中所有函数应为公有,不应存在私有函数
 */
class Demo {
public:
    Demo();
    ~Demo();
    double foo(const double x);
private:
    class DemoImpl;
    DemoImpl* m_pImpl;
};

接口的cpp文件代码:

//  ----------------------------------------------------------------------------
/**
 * @file  Demo.cpp
 */
#include "Demo.h"
#include "DemoImpl.h"

Demo::Demo() : m_pImpl( new Demo() ) {}

Demo::~Demo()
{
    if (m_pImpl) {
        delete m_pImpl;
    }
}

double Demo::foo(const double x) 
{ 
    return m_pImpl->foo(x); 
}

具体实现头文件代码:

//  ----------------------------------------------------------------------------
/**
 * @file  DemoImpl.h
 */
class DemoImpl {
public:
    DemoImpl() = default;
    double foo(const double x);
private:
    double operateOf(const std::vector<double>& bar, const double x);
    // ...
private:
    std::vector<double> m_bar;
};

具体实现的cpp文件代码:

//  ----------------------------------------------------------------------------
/**
 * @file  DemoImpl.cpp
 */
#include "DemoImpl.h"

double DemoImpl::foo(const double x)
{
    // ...
    double y = this->operateOf(m_bar, x);
    // ...
}

double DemoImpl::operateOf(std::vector<double>& bar, const double x)
{
    // ...
}

调用者代码:

#include "Demo.h"

int main()
{
    Demo demo; 
    double x = 0.0;
    double y = demo.foo(x);
    
    // ...
    return 0;
}

其他形式接口

C语言接口

由于各语言一般都支持调用C接口,所以C++实现的模块提供C接口是最为通用的,而C++与C语言有着紧密的联系,故其导出为C接口十分方便,其示例代码如下:

//  ----------------------------------------------------------------------------
/**
 * @file  foo.h
 */
#ifdef _cplusplus
extern "C" {
#endif
 
double foo(double x);
 
#ifdef _cplusplus
}
#endif


//  ----------------------------------------------------------------------------
/**
 * @file  foo.cpp
 */
#include "foo.h"
#include "Demo.h"

double foo(double x)
{
    Demo demo;   
    double y = demo.foo(x);
    return y;
}

纯虚接口

还有一种接口方式就是纯虚接口,即设计一个纯虚类作为接口,让模块中具体实现类均继承此类,其优点是当模块中要添加新的类时,只需新的类成为纯虚类的子类即可,而调用者代码无需修改,降低了模块间的耦合;

其典型实现与前述文章中 工厂模式 类似,故不在此处赘述,若要详细了解,可参考前述文中代码;

总结

在C++开发中,常需要避免模块间的编译依赖,惯用方式为 pImpl 技法;

若要模块接口更为通用,可使用C接口;

而纯虚接口常用于面向对象设计中的多态需求;

实践中,根据不同要求,需恰当使用这些接口,以降低模块间耦合性;


本文作者: 王同学