设计模式之PIMPL模式
PIMPL是指pointer to implementation,又称作“编译防火墙”,是实现“将文件间的编译依存关系降至最低”的方法之一。PIMPL模式是一种减少代码依赖和编译时间的C++编程技巧,其基本思想是将一个外部可见类的实现细节(一般是通过私有的非虚成员)放在一个单独的实现类中,在可见类中通过一个私有指针来间接访问该类型。
一、PIMPL模式实现
代码实现
实际项目的需求:希望Line类的实现全部隐藏,在源文件中实现,再将其打包成库文件,交给第三方使用。
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;
};
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();
}
LineTest.cc
在测试文件中创建Line对象(最外层),使用Line对外提供的接口,但是不知道具体的实现。
#include "Line.hpp"
#include <iostream>void test0(){Line line(1,2,3,4);line.printLine();
}
int main()
{test0(); return 0;
}
- 打包库文件,将库文件和头文件交给第三方
g++ -c LineImpl.cc
ar rcs libLine.a LineImpl.o生成 libLine.a 库文件
编译:g++ Line.cc(测试文件) -L(加上库文件地址) -lLine(就是库文件名中的 lib 缩写为 l,不带后缀)
此时的编译指令为 g++ Line.cc -L. -lLine
内存结构

构造析构顺序分析
运行程序结果如下:
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模式优点与目的:
-
信息隐藏
私有成员完全可以隐藏在共有接口之外,尤其对于闭源API的设计尤其的适合。同时,很多代码会应用平台依赖相关的宏控制,这些琐碎的东西也完全可以隐藏在实现类当中,给用户一个简洁明了的使用接口。 -
加速编译
这通常是用pImpl手法的最重要的收益,称之为编译防火墙(compilation firewall),主要是阻断了类的接口和类的实现两者的编译依赖性。这样,类用户不需要额外include不必要的头文件,同时实现类的成员可以随意变更,而公有类的使用者不需要重新编译。 -
更好的二进制兼容性
对于使用pImpl手法,只要头文件中的接口不变,实现文件可以随意修改,修改完毕只需要将新生成的库文件交给第三方即可,所以实现做出重大变更的情况下,pImpl也能够保证良好的二进制兼容性,可以实现库的平滑升级,这是pImpl的精髓所在。 -
惰性分配
实现类可以做到按需分配或者实际使用时候再分配,从而节省资源提高响应。
参考博文:
https://www.cnblogs.com/sggggr/p/16939996.html