设计模式之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
标签:LineImpl,Point,int,模式,PIMPL,析构,Line,设计模式,构造函数 From: https://www.cnblogs.com/Invinc-Z/p/18564925