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

yizhihongxing

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日

相关文章

  • php日期格式化方法详解

    PHP日期格式化方法详解 在开发中,我们常常需要对日期进行格式化,比如要将日期转成字符串,或者将字符串转成日期对象。PHP 提供了丰富的日期格式化方法,本文将对常用的格式化方法进行详细讲解。 将日期时间格式化为字符串 使用 PHP 内置的 date 函数可以将日期时间格式化为字符串。该函数的第一个参数为格式化字符串,用于指定输出的格式。 下面是一些常用的格式…

    其他 2023年3月28日
    00
  • SpringBoot加载读取配置文件过程详细分析

    SpringBoot加载读取配置文件的过程 SpringBoot在启动过程中会对其内部的配置文件和外部的配置文件进行加载,这里主要介绍其在启动过程中读取配置文件的过程。 具体的过程如下: 第一步:SpringBoot在启动过程中会先加载其内部的配置文件,包括 application.properties 和 application.yml。如果两个文件都存在…

    other 2023年6月25日
    00
  • 基于layui轮播图满屏是高度自适应的解决方法

    为了让你更好地理解“基于layui轮播图满屏是高度自适应的解决方法”,我为你准备了以下的详细攻略: 1.准备工作 在开始实现这个方法之前,我们首先需要准备以下工作: 一个基于layui框架的轮播图组件 一个能够实现高度自适应的外层容器 一个设备宽度的全局变量 2.实现方法 接下来,我们就可以开始着手实现高度自适应的轮播图了。具体的实现方法如下: 2.1.设置…

    other 2023年6月27日
    00
  • 解决Python列表字符不区分大小写的问题

    解决Python列表字符不区分大小写的问题攻略 在Python中,列表是一种常用的数据结构,但是默认情况下,列表中的字符是不区分大小写的。如果你需要在列表中进行大小写敏感的操作,可以按照以下攻略进行处理。 1. 使用列表推导式 列表推导式是一种简洁的方式来创建新的列表。你可以使用列表推导式来创建一个新的列表,其中所有的字符都是区分大小写的。 # 示例1: 创…

    other 2023年8月17日
    00
  • 相机SD卡提示未格式化 文件系统损坏 照片怎么恢复的解决方法介绍

    相机SD卡提示未格式化 文件系统损坏 照片恢复解决方法 问题描述 当我们将相机SD卡插入电脑或相机时,有可能会遇到提示“未格式化”、“文件系统损坏”的情况,这时候我们就无法访问SD卡上的照片和其他文件,非常困扰。下面我将介绍几种解决该问题的方法。 方法一:使用数据恢复软件 在计算机上安装数据恢复软件,比如Recuva(免费)、Stellar Data Rec…

    other 2023年6月27日
    00
  • 利用Android封装一个有趣的Loading组件

    让我详细讲解如何利用Android封装一个有趣的Loading组件。 1. 需求分析 在开始编写代码之前,我们需要先确定需求并做好计划。首先考虑的是我们需要的样式和效果,然后明确组件将被用于哪些场景和视图中。 假设我们需要一个有趣的Loading组件,它应该在加载数据时显示并在数据加载完成后自动消失。此外,它应该有一些视觉效果,比如动画和颜色渐变等。 2. …

    other 2023年6月25日
    00
  • uniapp实现a标签跳转

    以下是“uniapp实现a标签跳转”的完整攻略: uniapp实现a标签跳转 在uniapp中,我们可以使用<navigator>标签来实现页面跳转。以下是两种常见的实现a标签跳转的方法: 1. 使用<navigator>标签 我们可以使用<navigator>标签来实现a标签跳转。以下是一个示例: <templat…

    other 2023年5月7日
    00
  • vueappend()方法

    Vue.append()方法是Vue.js框架中的一个实例方法,用于将一个组件实例添加到指定的DOM元素中。以下是一个完整攻略,介绍了如何使用Vue.append()方法。 步骤1:创建Vue组件实例 首先,需要创建一个Vue组件实例。以下是一个示例: var MyComponent = Vue.extend({ template: ‘<div>…

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