设计模式之PIMPL模式

设计模式之PIMPL模式

PIMPL是指pointer to implementation,又称作“编译防火墙”,是实现“将文件间的编译依存关系降至最低”的方法之一。PIMPL模式是一种减少代码依赖和编译时间的C++编程技巧,其基本思想是将一个外部可见类的实现细节(一般是通过私有的非虚成员)放在一个单独的实现类中,在可见类中通过一个私有指针来间接访问该类型。

一、PIMPL模式实现

代码实现

实际项目的需求:希望Line类的实现全部隐藏,在源文件中实现,再将其打包成库文件,交给第三方使用。

  1. Line.hpp 头文件只给出接口
#pragma once
class Line
{
public:
    Line(int x1,int y1, int x2, int y2);
    ~Line();
    void printLine() const; // 打印 Line 对象的信息

private:
    class LineImpl; // 类的前向声明
    LineImpl* _pImpl;
};
  1. LineImpl.cc 在实现文件中进行具体实现,使用嵌套类的结构(LineImpl是Line的内部类,Point是LineImpl的内部类),Line类对外公布的接口都是使用LineImpl进行具体实现的。
#include "Line.hpp"
#include <iostream>
using namespace std;

class Line::LineImpl{
public:
    class Point{
    public:
        Point(int x, int y);
        ~Point();
        void print() const;
    private:
        int _ix;
        int _iy;
    };
public:
    LineImpl(int x1, int y1, int x2, int y2);
    ~LineImpl();
    void printLine() const;
private:
    Point _pt1;
    Point _pt2;
};

Line::LineImpl::Point::Point(int x,int y):_ix(x),_iy(y){
    cout << "Point(int,int)" << endl;
}
Line::LineImpl::Point::~Point(){
          cout << "~Point()" << endl;
}
void Line::LineImpl::Point::print() const{
    cout << "(" << _ix << "," << _iy << ")";
}

Line::LineImpl::LineImpl(int x1, int y1, int x2, int y2):_pt1(x1,y1),_pt2(x2,y2){
    cout << "Line::LineImpl::LineImpl(int,int,int,int)" << endl;
}
Line::LineImpl::~LineImpl(){
    cout << "Line::LineImpl::~LineImpl()" << endl;
}
void Line::LineImpl::printLine() const{
    _pt1.print();
    cout << "--->";
    _pt2.print();
    cout << endl;
}

Line::Line(int x1, int y1, int x2, int y2):_pImpl(new LineImpl(x1,y1,x2,y2)){
    cout << "Line::Line(int*4)" << endl;
}
Line::~Line(){
    cout << "Line::~Line()" << endl;
    if(_pImpl){
        delete _pImpl;
        _pImpl = nullptr;
    }
}
void Line::printLine() const{
    _pImpl->printLine();
} 
  1. LineTest.cc 在测试文件中创建Line对象(最外层),使用Line对外提供的接口,但是不知道具体的实现。
#include "Line.hpp"
#include <iostream>

void test0(){
    Line line(1,2,3,4);
    line.printLine();
}
int main()
{
    test0();                                                                                     
    return 0;
}
  1. 打包库文件,将库文件和头文件交给第三方
g++ -c LineImpl.cc
ar rcs libLine.a LineImpl.o

生成 libLine.a 库文件
编译:g++ Line.cc(测试文件) -L(加上库文件地址) -lLine(就是库文件名中的 lib 缩写为 l,不带后缀)
此时的编译指令为 g++ Line.cc -L. -lLine

内存结构

image-20241123173924894

构造析构顺序分析

运行程序结果如下:

image-20241123173117230

Line line(1,2,3,4);

Line::Line(int x1, int y1, int x2, int y2):_pImpl(new LineImpl(x1,y1,x2,y2)){
    cout << "Line::Line(int*4)" << endl;
}

Line::LineImpl::LineImpl(int x1, int y1, int x2, int y2):_pt1(x1,y1),_pt2(x2,y2){
    cout << "Line::LineImpl::LineImpl(int,int,int,int)" << endl;
}

Line::LineImpl::Point::Point(int x,int y):_ix(x),_iy(y){
    cout << "Point(int,int)" << endl;
}

构造顺序分析:首先 line调用Line的构造函数,在line构造函数的初始化列表中调用LineImpl的构造函数,然后再在LineImpl的构造函数初始化列表中执行Point的构造函数,Point类的构造函数执行完成之后,返回继续执行LineImpl的构造函数的函数体部分,在LineImpl构造执行完成之后,继续执行Line的函数体部分。

void test0(){
    Line line(1,2,3,4);
}

Line::LineImpl::Point::~Point(){
          cout << "~Point()" << endl;
}

Line::LineImpl::~LineImpl(){
    cout << "Line::LineImpl::~LineImpl()" << endl;
}

Line::~Line(){
    cout << "Line::~Line()" << endl;
    if(_pImpl){
        delete _pImpl;
        _pImpl = nullptr;
    }
}

析构顺序分析:在line离开test0()函数的右括号之后,line的生命周期结束,开始执行Line的析构函数,首先输出Line析构的提示信息,接着delete指向LineImpl的指针。在delete的操作中,首先调用该指针指向对象LineImpl的析构函数,以完成资源清理工作,Point作为LineImpl的数据成员子对象在其父对象析构时也开始析构,析构完成后回到LineImpl完成析构收尾工作,当LineImpl析构完成后,delete 进行第二步工作,释放对象占用的内存,最后Line构造执行结束。

总结:构造析构的顺序都是从父对象开始执行,其顺序可以想象成一个环形。

二、PIMPL模式优点与目的:

  1. 信息隐藏
    私有成员完全可以隐藏在共有接口之外,尤其对于闭源API的设计尤其的适合。同时,很多代码会应用平台依赖相关的宏控制,这些琐碎的东西也完全可以隐藏在实现类当中,给用户一个简洁明了的使用接口。

  2. 加速编译
    这通常是用pImpl手法的最重要的收益,称之为编译防火墙(compilation firewall),主要是阻断了类的接口和类的实现两者的编译依赖性。这样,类用户不需要额外include不必要的头文件,同时实现类的成员可以随意变更,而公有类的使用者不需要重新编译。

  3. 更好的二进制兼容性
    对于使用pImpl手法,只要头文件中的接口不变,实现文件可以随意修改,修改完毕只需要将新生成的库文件交给第三方即可,所以实现做出重大变更的情况下,pImpl也能够保证良好的二进制兼容性,可以实现库的平滑升级,这是pImpl的精髓所在。

  4. 惰性分配
    实现类可以做到按需分配或者实际使用时候再分配,从而节省资源提高响应。

参考博文:

https://www.cnblogs.com/sggggr/p/16939996.html

posted @   Invinc-Z  阅读(170)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· AI Agent开发,如何调用三方的API Function,是通过提示词来发起调用的吗
点击右上角即可分享
微信分享提示