首页 > 编程语言 >C++接口工程实践

C++接口工程实践

时间:2022-12-05 15:15:08浏览次数:72  
标签:const Network 实现 实践 接口 C++ NetworkImpl

https://zhuanlan.zhihu.com/p/213902091
还没有学习完
简介:程序开发的时候经常会使用到接口。众所周知,C++语言层面并没有接口的概念,但并不意味着C++不能实现接口的功能。相反,正是由于C++语言没有提供标准的接口,导致实际实现接口的方法多种多样。那么C++有哪些实现接口的方法呢,不同的方法又适用于哪些场景呢?本文分享在C++接口工程实践上的一些探索心得。

一接口的分类

接口按照功能划分可以分为调用接口和回调接口:

调用接口

一段代码、一个模块、一个程序库、一个服务等(后面都称为系统),对外提供什么功能,以接口的形式暴露出来,用户只需要关心接口怎么调用,不用关心具体的实现,即可使用这些功能。这类被用户调用的接口,称为调用接口。

调用接口的主要作用是解耦,对用户隐藏实现,用户只需要关心接口的形式,不用关心具体的实现,只要保持接口的兼容性,实现上的修改或者升级对用户无感知。解耦之后也方便多人合作开发,设计好接口之后,各模块只通过接口进行交互,各自完成各自的模块即可。

回调接口

系统定义接口,由用户实现,注册到系统中,系统有异步事件需要通知用户时,回调用户注册的接口实现。系统定义接口的形式,但无需关心接口的实现,而是接受用户的注册,并在适当的时机调用。这类由系统定义,用户实现,被系统调用的接口,称为回调接口。

回调接口的主要作用是异步通知,系统定义好通知的接口,并在适当的时机发出通知,用户接收通知,并执行相应的动作,用户动作执行完后控制权交还给系统,用户动作可以给系统返回一些数据,以决定系统后续的行为。

调用接口

我们以一个Network接口为例,说明C++中的调用接口的定义及实现,示例如下:

class Network
{
public:
    bool send(const char* host, 
              uint16_t port, 
              const std::string& message);
}

Network接口现在只需要一个send接口,可以向指定地址发送消息。下面我们用不同的方法来定义Network接口。

虚函数

虚函数是定义C++接口最直接的方式,使用虚函数定义Network接口类如下:

class Network
{
public:
    virtual bool send(const char* host, 
                      uint16_t port, 
                      const std::string& message) = 0;

    static Network* New();

    static void Delete(Network* network);
}

将send定义为纯虚函数,让子类去实现,子类不对外暴露,提供静态方法New来创建子类对象,并以父类Network的指针形式返回。接口的设计一般遵循对象在哪创建就在哪销毁的原则,因此提供静态的Delete方法来销毁对象。因为对象的销毁封装在接口内部,因此Network接口类可以不用虚析构函数。

使用虚函数定义接口简单直接,但是有很多弊端:

  • 虚函数开销:虚函数调用需要使用虚函数表指针间接调用,运行时才能决定调用哪个函数,无法在编译链接期间内联优化。实际上调用接口在编译期间就能确定调用哪个函数,无需虚函数的动态特性。

  • 二进制兼容:由于虚函数是按照索引查询虚函数表来调用,增加虚函数会造成索引变化,新接口不能在二进制层面兼容老接口,而且由于用户可能继承了Network接口类,在末尾增加虚函数也有风险,因此虚函数接口一经发布,难以修改。

指向实现的指针

指向实现的指针是C++比较推荐的定义接口的方式,使用指向实现的指针定义Network接口类如下:

class NetworkImpl;
class Network
{
public:
    bool send(const char* host, 
              uint16_t port, 
              const std::string& message);

    Network();

    ~Network();

private:
    NetworkImpl* impl;
}

Network的实现通过impl指针转发给NetworkImpl,NetworkImpl使用前置声明,实现对用户隐藏。使用指向实现的指针的方式定义接口,接口类对象的创建和销毁可以由用户负责,因此用户可以选择将Network类的对象创建在栈上,生命周期自动管理。

使用指向实现的指针定义接口具有良好的通用性,用户能够直接创建和销毁接口对象,并且增加新的接口函数不影响二进制兼容性,便于系统的演进。

指向实现的指针增加了一层调用,尽管对性能的影响几乎可以忽略不计,但不太符合C++的零开销原则,那么问题来了,C++能否实现零开销的接口呢?当然可以,即下面要介绍的隐藏的子类。

隐藏的子类

隐藏的子类可以实现零开销的接口,思想非常简单。调用接口要实现的目标是解耦,主要就是隐藏实现,也即隐藏接口类的成员变量,如果能将接口类的成员变量都移到另一个隐藏的实现类中,接口类就不需要任何成员变量,也就实现了隐藏实现的目的。隐藏的子类就是这个隐藏的实现类,使用隐藏的子类定义Network接口类如下:

class Network
{
public:
    bool send(const char* host, 
              uint16_t port, 
              const std::string& message);

    static Network* New();

    static void Delete(Network* network);

protected:
    Network();

    ~Network();
}

Network接口类只有成员函数(非虚函数),没有成员变量,并且构造函数和析构函数都申明为protected。提供静态方法New创建对象,静态方法Delete销毁对象。New方法的实现中创建隐藏的子类NetworkImpl的对象,并以父类Network指针的形式返回。NetworkImpl类中存放Network类的成员变量,并将Network类声明为friend:

class NetworkImpl : public Network
{
    friend class Network;

private:
    //Network类的成员变量
}

Network的实现中,创建隐藏的子类NetworkImpl的对象,并以父类Network指针的形式返回,通过将this强制转换为NetworkImpl的指针,访问成员变量:

bool Network::send(const char* host, 
                   uint16_t port, 
                   const std::string& message)
{
    NetworkImpl* impl = (NetworkImpl*)this;
    //通过impl访问成员变量,实现Network
}

static Network* New()
{
    return new NetworkImpl();
}

static void Delete(Network* network)
{
    delete (NetworkImpl*)network;
}

使用隐藏的子类定义接口同样具有良好的通用性和二进制兼容性,同时没有增加任何开销,符合C++的零开销原则。
三 回调接口
同样以Network接口为例,说明C++中的回调接口的定义及实现,示例如下:

class Network
{
public:
    class Listener
    {
    public:
        void onReceive(const std::string& message);
    }

    bool send(const char* host, 
              uint16_t port, 
              const std::string& message);

    void registerListener(Listener* listener);
}

现在Network需要增加接收消息的功能,增加Listener接口类,由用户实现,并注册其对象到Network中后,当有消息到达时,回调Listener的onReceive方法。

标签:const,Network,实现,实践,接口,C++,NetworkImpl
From: https://www.cnblogs.com/sggggr/p/16952298.html

相关文章

  • 5个Ajax最佳实践
    导读:通过对5个最佳实践的学习,开发人员可将其应用到日常的AsynchronousJavaScript+XML(Ajax)开发工作中。文章包括了数据格式、错误处理、以及一些采用Ajax的RichIntern......
  • 中国敏捷十年实践者分享:敏捷教练的自我修为
    摘要:在TiD2022质量竞争力大会上来自华为云开发者联盟的首席布道师带来“敏捷教练的自我修为”主题分享。本文分享自华为云社区《​​中国敏捷十年实践者分享:敏捷教练的自我......
  • 中国敏捷十年实践者分享:敏捷教练的自我修为
    摘要:在TiD2022质量竞争力大会上来自华为云开发者联盟的首席布道师带来“敏捷教练的自我修为”主题分享。本文分享自华为云社区《中国敏捷十年实践者分享:敏捷教练的自我修......
  • 获取网易云音乐开放接口api的推荐歌单
    网易云音乐开放api接口网址:https://binaryify.github.io/NeteaseCloudMusicApi/#/?id=neteasecloudmusicapi项目地址:https://github.com/Binaryify/NeteaseCloudMusicAp......
  • 在c#中调用c++的dll崩溃了,try catch 怎么获取异常?
    在framework框架下,通过添加HandleProcessCorruptedStateExceptionsAttribute属性来解决这个问题,(.netcore1.0到3.1之前,不支持从损坏的进程状态异常中恢复,即trycatch没有......
  • C++ IMPL模式解析(下)
    二进制兼容在上一章结尾处提到了二进制兼容的概念,这里先说说二进制兼容的问题。为什么是二进制兼容简单说,就是我的可执行程序调用你的动态库(so/dll),若动态库发生改动,我......
  • c++ vector resize 和 assign
    resize改变大小resize(n,value),不够的部分填充为valuevector<int>nums{1,2,3,4,5,6};nums.resize(3,100);//size缩小,保持原状->{1,2,3}nums.resi......
  • C++获取一年中所有周信息
    std::map<int,std::pair<CTime,CTime>>YearOfWeek(intyear){std::map<int,std::pair<CTime,CTime>>weekMap;CTimetime(year,1,1,0,0,0);in......
  • JDK的dt.jar和Java BeanInfo接口
    在JAVA_HOME/lib下面有两个比较重要的jar文件,tools.jar和dt.jar。 tools.jar在上篇文章中做了简单的介绍。这里来介绍下dt.jar。在Oracle官方网站搜dt.jar,找到JDKand......
  • (收藏)接口限流实践
    一、问题描述 某天A君突然发现自己的接口请求量突然涨到之前的10倍,没多久该接口几乎不可使用,并引发连锁反应导致整个系统崩溃。如何应对这种情况呢?生活给了我们答案:比......