C++接口文件小技巧之PIMPL详解

C++接口文件小技巧之PIMPL详解

PIMPL(Pointer to Implementation)

PIMPL模式(指针实现标准库技术)是一种C++的编程技巧,也成为“编译期实现技术”(CTT)。指使用一个指针来指向一个接口类的指针,通过这个指针向实现类的指针,实现对实现类的访问。

PIMPL主要使用技术:

  • 前置声明提高编译速度,减少编译时间
  • 指针类实现事件管理

PIMPL模式将接口与实现分开,更加灵活,降低了代码依赖性,也更容易维护。

使用PIMPL模式的好处

  • 降低头文件依赖,提高编译速度,减少编译时间
  • 降低实现类的公开程度,提高代码的安全性
  • 只需修改实现类的内容时,不需要重新编译接口

示例1:使用PIMPL模式实现字符串

下面是一个使用PIMPL模式实现字符串的示例,先定义string.h头文件:

// string.h

class StringImpl;
class String{
public:
    String();
    ~String();
    int Length();
    void Append(char ch);
    void Append(const char* str);
private:
    StringImpl* pimpl_;
};

然后定义string.cpp文件:

// string.cpp

#include "string.h"
#include <cstring>

class StringImpl{
public:
    StringImpl()
        : data_(new char[size_]), size_(0), capacity_(default_capacity_){}
    ~StringImpl(){ delete[] data_;}
    int Length(){ return size_;}
    void Append(char ch){
        EnsureCapacity(1);
        data_[size_++] = ch;
    }
    void Append(const char* str){
        auto length = static_cast<int>(strlen(str));
        EnsureCapacity(length);
        std::memcpy(data_ + size_, str, length);
        size_ += length;
    }
private:
    void EnsureCapacity(int length){
        if (size_ + length > capacity_){
            capacity_ = std::max(capacity_ * 2, size_ + length);
            char* new_data = new char[capacity_];
            std::memcpy(new_data, data_, size_);
            delete[] data_;
            data_ = new_data;
        }
    }
    static const int default_capacity_ = 16;
    int size_;
    int capacity_;
    char* data_;
};

String::String():pimpl_(new StringImpl){}
String::~String(){ delete pimpl_;}
int String::Length(){ return pimpl_->Length();}
void String::Append(char ch){ pimpl_->Append(ch);}
void String::Append(const char* str){ pimpl_->Append(str);}

上面的代码中,String类没有定义析构函数,因为析构函数会自动将StringImpl的析构函数调用。我们只需要在String类的后面定义析构函数,即可将StringImpl的析构函数调用。

示例2:PIMPL模式管理回调函数

下面是一个使用PIMPL模式管理回调函数的示例,我们先定义回调函数的接口:

// callback.h

class CallbackImpl{
public:
    virtual ~CallbackImpl(){}
    virtual void Run() = 0;
};

class Callback{
public:
    Callback();
    Callback(const Callback& other);
    ~Callback();
    Callback& operator=(const Callback& other);
    void Run();
private:
    CallbackImpl* pimpl_;
};

然后定义callback.cpp文件:

//callback.cpp

#include "callback.h"
#include <memory>

class CallbackHolder : public CallbackImpl{
public:
    explicit CallbackHolder(std::function<void()> callback)
        : callback_(callback){}
    void Run() override{
        callback_();
    }
private:
    std::function<void()> callback_;
};

Callback::Callback():pimpl_(nullptr){}
Callback::Callback(const Callback& other){
    if (other.pimpl_){
        pimpl_ = other.pimpl_;
    }
}
Callback::~Callback(){
    delete pimpl_;
}
Callback& Callback::operator=(const Callback& other){
    if (this == &other) return *this;
    delete pimpl_;
    pimpl_ = nullptr;
    if (other.pimpl_){
        pimpl_ = other.pimpl_;
    }
    return *this;
}
void Callback::Run(){
    if (pimpl_){
        pimpl_->Run();
    }
}
void CreateCallback(std::function<void()> callback, Callback* out_callback){
    // 注意这里malloc会申请内存但不会对内存进行初始化,所以需要在使用前对内存进行初始化
    void* mem = std::malloc(sizeof(CallbackHolder));
    new(mem) CallbackHolder(callback);
    out_callback->pimpl_ = static_cast<CallbackImpl*>(mem);
}

上面的代码中,Callback类没有定义构造函数,因为构造函数会自动将CallbackHolder的构造函数调用。我们只需要在Callback类的后面定义构造函数,即可将CallbackHolder的构造函数调用。

总结

PIMPL模式是一种非常实用的C++技术,它将接口与实现进行有效分离,降低了代码依赖性,提高了代码的可维护性和扩展性。但是PIMPL也有一定的缺点,比如会增加代码复杂度,增加调试难度,以及需要动态申请内存等问题。所以在使用PIMPL模式时需要综合考虑各种因素,权衡利弊。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:C++接口文件小技巧之PIMPL详解 - Python技术站

(0)
上一篇 2023年6月26日
下一篇 2023年6月26日

相关文章

  • 详解Go语言变量作用域

    详解Go语言变量作用域 在Go语言中,变量的作用域决定了它在程序中的可见性和可访问性。变量的作用域可以分为全局作用域和局部作用域。本攻略将详细讲解Go语言变量作用域的概念和规则,并提供两个示例来说明。 全局作用域 全局作用域是指在整个程序中都可以访问的变量。在Go语言中,全局变量声明在函数体外部,可以在任何函数中使用。 示例1: package main i…

    other 2023年7月29日
    00
  • 深入理解Java中观察者模式与委托的对比

    本篇攻略旨在对比Java中观察者模式与委托模式的差异,深入理解它们的作用及使用方法。 一、观察者模式 1. 模式概述 观察者模式是一种设计模式,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象,当主题对象状态发生变化时,会通知所有观察者,使它们能够自动更新。 2. 示例说明 下面是一个简单的示例,来演示观察者模式的实现过程。 // 主题接…

    other 2023年6月26日
    00
  • 人脸识别-论文阅读-arcface及其由来(sphereface、cosface)

    人脸识别-论文阅读-arcface及其由来(sphereface、cosface)攻略 1. 了解人脸识别算法 人脸识别是计算机视觉领域的一个重要研究方向。在人脸识别中,人脸特征提取是关键的步骤。深度学习是当前人脸识别领域的主流方法,其中基于深度学习的人脸识别算法可以分为两类:基于特征提取的方法和基于度量学习的方法。基于特征提取的方法将人脸图像映射到一个低维…

    other 2023年5月7日
    00
  • 使用postman进行接口自动化测试

    使用Postman进行接口自动化测试攻略 Postman是一款功能强大的API开发和测试工具,它提供了丰富的功能来进行接口自动化测试。下面是使用Postman进行接口自动化测试的完整攻略。 步骤一:安装和设置Postman 下载并安装Postman:从Postman官方网站(https://www.postman.com)下载并安装适合您操作系统的Postm…

    other 2023年7月29日
    00
  • asp.net 动态添加多个用户控件

    ASP.Net中动态添加多个用户控件的过程需要以下步骤: 为用户控件创建一个ASP.Net Web应用程序,并确保已经添加了所需的用户控件。 在Web应用程序的页面代码中,使用LiteralControl对象在页面上动态添加用户控件。LiteralControl是一个空间,它允许您以纯文本方式向页面添加HTML标记和其他内容。 在Page_Load事件中,使…

    other 2023年6月27日
    00
  • 关于Oracle12C默认用户名system密码不正确的解决方案

    问题描述: 在使用Oracle12C时,有时候会遇到默认用户名system的密码不正确的问题,导致无法使用数据库。这可能是由于安装过程中出现问题或者其他原因引起的,需要我们进行相应的解决方案。 解决方案: Oracle12C默认用户名system密码不正确时,我们可以通过以下步骤进行解决: 步骤一:使用SQL*Plus登录数据库 首先,我们需要使用SQL*P…

    other 2023年6月27日
    00
  • ppt2013自定义功能区怎么添加项目卡和命令?

    要添加项目卡和命令,需要按照以下步骤进行操作: 第一步:打开“自定义功能区”选项 在ppt2013中,依次点击“文件-选项-自定义功能区”,打开“自定义功能区”的选项卡。这里可以选择要添加项目卡和命令的位置。 第二步:添加项目卡 点击“新建标签”按钮,创建一个新的标签。 命名标签,例如“我的工具箱”。 在标签下方的“新建项目卡”处点击“新建”,创建一个新的项…

    other 2023年6月25日
    00
  • android dialog自定义实例详解

    Android Dialog自定义实例详解 在Android应用程序中,我们通常需要使用Dialog来显示一些重要的提示信息或者需要让用户进行操作的界面。Android提供了一些默认的Dialog,例如AlertDialog、ProgressDialog等等,但是这些默认的Dialog不能够满足我们所有的需求,因此我们需要自定义Dialog。下面我们将详细介…

    other 2023年6月25日
    00
合作推广
合作推广
分享本页
返回顶部