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日

相关文章

  • 你可能会用到的16个Linux命令

    下面是对于“你可能会用到的16个Linux命令”完整攻略的详细讲解: 命令介绍 1. ls 用于显示当前文件夹中的文件和目录列表。 示例: ls 2. cd 用于切换工作目录。 示例: cd Documents/ 3. pwd 用于显示当前工作目录的完整路径。 示例: pwd 4. mkdir 用于创建新的目录。 示例: mkdir NewDirectory…

    other 2023年6月26日
    00
  • C语言实例梳理讲解常用关键字的用法

    C语言实例梳理讲解常用关键字的用法攻略 介绍 C语言作为计算机领域中最常见的编程语言之一,具有广泛的应用和应试范围。在学习C语言的过程中,了解语言中常用的关键字以及它们的使用方法是非常重要的。本攻略将通过实例讲解的方式,从常用关键字入手,帮助读者了解C语言的关键字及其使用方法。 常用关键字的讲解 if if 是一种条件语句,用于判断一个表达式的值是否为 tr…

    other 2023年6月27日
    00
  • Debian 9.4 系统安装及Jdk等工具安装方法

    下面是完整的Debian 9.4系统安装及Jdk等工具安装方法攻略。 安装Debian 9.4 下载系统镜像 首先,我们需要在Debian官网上下载Debian 9.4的系统镜像文件。在此,以64位AMD架构为例: wget -c http://mirrors.ustc.edu.cn/debian-cd/current/amd64/iso-cd/debian…

    other 2023年6月27日
    00
  • 在windows上安装不同(两个)版本的Mysql数据库的教程详解

    安装不同版本的MySQL数据库在Windows上并不难。为了实现这一目的,可以将不同版本的MySQL安装在不同的文件夹中。接下来,详细讲解在Windows上安装不同版本的MySQL数据库的过程步骤。 步骤1:下载不同版本的MySQL 首先,需要从MySQL官方网站下载多个不同版本的MySQL安装文件,选择相应的Windows版本,同时根据需要选择32位或64…

    other 2023年6月27日
    00
  • Eclipse通过jdbc连接sqlserver2008数据库的两种方式

    在Eclipse中连接SQL Server 2008数据库,可以使用两种方式:JDBC驱动程序和Data Tools Platform(DTP)插件。下面将详细介绍这两种方式的连接方法,并提供两个示例说明。 使用JDBC驱动程序连接SQL Server 2008数据库 步骤1:下载JDBC驱动程序 首先需要下载SQL Server 2008的JDBC驱动程序…

    other 2023年5月5日
    00
  • 详解android 中animation-list 动画的应用

    详解Android中animation-list动画的应用 animation-list是Android中一种用于创建帧动画的XML资源。它允许您定义一系列帧,并按照指定的顺序播放它们,从而创建动画效果。下面是详细的攻略,包含两个示例说明。 步骤1:创建animation-list资源文件 首先,您需要创建一个XML文件来定义animation-list资源…

    other 2023年8月21日
    00
  • 关于utf8:将utf-8转换为ascii

    以下是关于“将UTF-8转换为ASCII”的完整攻略,过程中包含两个示例。 背景 在编程中,有时需要将UTF-8编码的字符串转换为ASCII编码的字符串。本攻略将介绍如何将UTF-8编码的字符串转换为ASCII编码的字符串。 基本原理 在Python中,可以使用encode()方法将UTF-8编码的字符串转为字节数组,然后使用decode()方法将字节数组转…

    other 2023年5月9日
    00
  • PhpStorm 如何优雅的调试Hyperf的方法步骤

    PHPStorm 是一款功能强大的 IDE,我们可以通过它快速地进行代码编辑、调试和测试。如果我们需要开发和调试 Hyperf 应用程序,这里介绍一种优雅的调试方法。 步骤: 安装 Hyperf Debug 插件 在代码编辑器 PHPStorm 中,找到 Settings -> Plugins 进入插件管理页面,搜索 Hyperf Debug 插件并安…

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