• C++ API设计之风格


    C++ API设计之风格


    C++ API中看似很小的修改,都可能会影响到生成的对象和库文件的二进制表示,如果客户想替换共享库使之工作,就不能简单的替换库文件了事,而往往需要重新编译源码

    创建类来表示API中的每个关键对象,同时提供这些类的方法

    此处的API风格指的是如何表现API的功能,以下4种:

    • C API
      • 可以用C编译器编译的API。这种API只包含一组自由函数以及辅助的数据结构和常量。这种风格的接口不包含对象或继承,因此被称为纯C模式
    • 面向对象的C++ API
      • 这种风格涉及对象(其中包含相关的数据与方法)的使用以及继承、封装和多态等概念的应用
    • 基于模板的API
      • 通过模板功能,C++也支持泛型编程和元编程。它支持以泛型类型的方式编写函数和数据结构,在以后使用时,泛型类型可以通过具体类型来实例化,从而实现特化
    • 数据驱动型API
      • 这类接口的特点是,将参数通过灵活的数据结构打包,连同命名的命令一起发送给处理程序,而不是调用特定的方法和自由函数

    C API

    C语言不支持对象封装和继承层次结构等概念,因此,纯C语法的API必须使用一组更为受限的语言特性来表示,比如typedef、结构体和全局命名空间中的函数调用等。因为C语言中没有namespace关键字,要避免与其他C库中的名字发生冲突,对这种风格的API而言,所有公开的函数和数据结构应该使用一个公共的前缀

    当然,也可以使用内部链接隐藏实现中的符号名,比如将符号名声明为静态的,这样它们的作用域就限制在.c文件之中了。通过这种方式,可以确保任何这样的函数都不会被导出到外部,从而不会导致符号冲突

    // c++ 示例
    class Stack
    {
    public:
        void Push(int val);
        int Pop();
        bool IsEmpty() const;
    private:
        int *mStack;
        int mCurSize;
    };
    
    // 纯C API
    struct Stack
    {
        int *mStack;
        int mCurSize;
    };
    
    void StackPush(struct Stack *stack, int val);
    int StackPop(struct Stack *stack);
    bool StackIsEmpty(const struct Stack *stack);
    
    // 进一步改进
    typedef struct Stack *StackPtr;
    
    void StackPush(StackPtr stack, int val);
    int StackPop(StackPtr stack);
    bool StackIsEmpty(const StackPtr stack);
    
    // 可以通过特定的API调用来完成数据库结构的创建与销毁
    StackPtr StackCreate();
    void StackDestory(StackPtr stack);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    C API的头文件中使用extern "C"限制,以便C++程序能够正确的编译和链接C API

    #ifdef _cplusplus
    extern "C" {
    #endif
    // C API声明
    #ifdef _cplusplus
    }
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    面向对象的C++ API

    过程式编程、泛型编程、函数式编程

    使用面向对象的C++概念创建二进制兼容的API是极为困难的

    基于模板的API

    模板可以用来编写在编译时生成代码或执行代码的程序(该技术称为元编程)

    模板可以在编译时执行一些工作,进而改进运行时性能

    #include 
    
    template 
    class Stack
    {
    public:
        void Push(T val);
        T Pop();
        bool IsEmpty() const;
        
    private:
        std::vector mStack;
    };
    
    // 可以定义一个typedef,这样就可以更方便地使用该模板实例了
    typedef Stack IntStack;
    
    IntStack *stack = new IntStack();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    模板实现方式的另一个选择是,利用C预处理器来定义一段文本,可以将其放入多个头文件中

    #include 
    
    #define DECLARE_STACK(Prefix, T) \
    class Prefix##Stack \
    { \
    public: \
        void Push(T val); \
        T Pop(); \
        bool IsEmpty() const; \
        
    private: \
        std::vector mStack; \
    }
    
    DECLARE_STACK(Int, int);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    模板提供了一种类型安全的在编译时生成代码的方式。你可以调试到类模板的时机代码行中。除非你要编写纯C API,无法使用模板,否则就应该避免使用预处理器来模拟模板

    模板的一个重要属性是,不同于使用继承时的动态(运行时)多态,它支持静态(编译时)多态

    不会像虚方法那样存在运行时代价

    模板进一步的益处,对于特定类型的实例类,可以特化它的某些方法

    template <>
    void Stack::Push(int val)
    {
        // 实现特定于int类型的压栈功能
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    基于模板的API的缺点

    • 最严重的问题是:类模板的定义通常必须出现在公开的头文件中
      • 因为要特化模板,编译器必须能够访问模板代码的完整定义,显而易见,这会暴露内部细节
    • 每当其他文件包含了类模板定义所在的头文件时,内联的代码都需要重新编译,生成的代码会被添加到每个使用该API的模块的目标文件。这会增加编译时间,并致使代码膨胀
    • 实际上有些情况下你可以使用显式实例化技术将模板的实现隐藏在.cpp文件中
    • 模板的另一个缺点是,模板代码中出现错误时,大多数编译器生成的报错信息都是冗长且令人困惑的,可用STLFilt

    相对于运行时开销而言,代码体积是需要优先考虑的因素,那么应该选择面向对象方案,而非模板。或者相反,如果运行时性能更为重要,那就应该选择模板

    数据驱动型API

    数据驱动型程序指的是:通过每次运行时提供不同的输入数据,它可以执行不同的操作

    优点

    • 对于API将来可能发生的变化,它的容错性更强
    • 可以更容易地支持数据驱动型测试技术p143

    API支持可变参数列表

    • 联合体
    • 继承
    • void *
  • 相关阅读:
    【数据结构】单链表OJ题
    javaWeb项目中:连接mysql的驱动问题
    MySQL如何输出发生死锁的SQL到日志文件
    Java.lang.Class类 getModifiers()方法有什么功能呢?
    Spring常见问题解决 - 同一个类型的单例Bean找到了两个?
    Java二叉树超详解(常用方法介绍)(2)
    Spring Boot 集成 MongoDB 简单使用
    先进制造aps专题九 中国aps行业分析
    单个脚本打包成jar可以直接运行
    grafana使用小结
  • 原文地址:https://blog.csdn.net/qq_29935433/article/details/126528731