编译防火墙——C++的Pimpl惯用法解析
来源:互联网 发布:英格拉姆弹跳数据 编辑:程序博客网 时间:2024/05/18 00:38
http://blog.csdn.net/lihao21/article/details/47610309
Pimpl(pointer to implementation, 指向实现的指针)是一种常用的,用来对“类的接口与实现”进行解耦的方法。这个技巧可以避免在头文件中暴露私有细节(见下图1),因此是促进API接口与实现保持完全分离的重要机制。但是Pimpl并不是严格意义上的设计模式(它是受制于C++特定限制的变通方案),这种惯用法可以看作桥接设计模式的一种特例。
图1: Pimpl惯用法,这里的公有类拥有一个私有指针,该指针指向隐藏的实现类
在类中使用Pimpl惯用法,具有如下优点:
- 降低耦合
- 信息隐藏
- 降低编译依赖,提高编译速度
- 接口与实现分离
为了实现Pimpl,我们先来看一种普通的类的设计方法。
假如我们要设计一书籍类Book,Book包含目录属性,并提供打印书籍信息的对外接口,Book设计如下:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
Book的使用者只需要知道print()接口,便可以使用Book类,看起来一切都很美好。
然而,当某一天,发现Book需要增加一标题属性,对Book类的修改如下:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
虽然使用print()接口仍然可以直接输出书籍的信息,但是Book类的使用者却不得不重新编译所有包含Book类头文件的代码。
为了隐藏Book类的实现细节,实现接口与实现的真正分离,可以使用Pimpl方法。
我们依然对Book类提供相同的接口,但Book类中不再包含原有的数据成员,其所有操作都由BookImpl类实现。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
在对外的头文件public.h中,只包含Book类的外部接口,将真正的实现细节被封装到BookImpl类。为了不对外暴露BookImpl类,将其声明为Book类的内嵌类,并声明为private。
BookImpl类的头文件如下。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
private.h并不需要提供给Book类的使用者,因此,如果往后需要重新设计书籍类的属性,外界对此一无所知,从而保持接口的不变性,并减少了文件之间的编译依赖关系。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
使用Book类的接口的方法如下:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
像Book类这样使用Pimpl的类,往往被称为handle class,BookImpl类作为实现类,被称为implementation class。
为简单实现起见,Book类省略了复制构造函数和复制赋值函数。在实际应用中,一般有两种可选方案解决Book的复制和赋值的语义问题。
(1) 禁止复制类
如果不打算让用户创建对象的副本,那么可以将对象声明为不可复制的。可以将复制构造函数和复制赋值函数声明为私有的,这样在复制或者赋值时就会产生编译错误。
以下代码通过声明私有的复制构造函数和复制赋值函数来使得对象不可以复制,不需要修改相关的.cpp文件。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
(2) 显示定义复制语义
如果希望用户能够复制采用Pimpl的对象,就应该声明并定义自己的复制构造函数和复制赋值函数。它们可以执行对象的深复制,即创建对象的副本,而非复制指针。
Pimpl惯用法最主要的缺点是,必须为你创建的每个对象分配并释放实现对象,这使对象增加了一个指针,handle class成员函数的每次调用都必须通过implementation class,这会增加一层间接性。在实际中你需要对这些开销进行权衡。
另外,采用了Pimpl的对象,编译器将不再能够捕获const方法中对成员变量的修改。这是由于成员变量现在存在于独立的对象中,编译器仅检查const方法中的pimpl指针是否发生变化,而不会检查pimpl指向的任何成员。
可以使用下图2来说明Pimpl方法在以上Book类设计的作用:
图2: Pimpl作为编译防火墙
由于Pimpl解除了接口与实现之间的耦合关系,从而降低文件间的编译依赖关系,Pimpl也因此常被称为“编译期防火墙“ 。
本文的示例代码可以通过以下链接下载: 下载链接
参考资料
- C++ Programming/Idioms: Pointer To Implementation (pImpl)
- The C++ Pimpl
- Compilation Firewalls
- 《Effective C++(第三版)》, Scott Meyers著, 侯捷译
- PIMPL模式
- 《C++ API设计》, Martin Reddy著,
- 编译防火墙——C++的Pimpl惯用法解析
- 编译防火墙——C++的Pimpl惯用法解析
- pimpl 惯用法的背后
- Exceptional C++读书笔记:编译器防火墙与Pimpl惯用法(一)
- 重构 — 改善既有的类图设计 条款9:用Pimpl惯用法隐藏细节
- 旧话重提:pImpl惯用手法的背后
- 旧话重提:pImpl惯用手法的背后
- 旧话重提:pImpl惯用手法的背后
- 旧话重提:pImpl惯用手法的背后
- 旧话重提:pImpl惯用手法的背后
- 旧话重提:pImpl惯用手法的背后
- 旧话重提:pImpl惯用手法的背后
- 旧话重提:pImpl惯用手法的背后
- Bridge模式/ Pimpl惯用法 实例
- 设计模式之pimpl惯用法C语言版(城门失火 殃及池鱼)
- extern "C"的惯用法
- extern "C"的惯用法
- extern "C"的惯用法
- EA&UML日拱一卒--活动图::入门
- CS224d Assignment1 答案, Part(2/4)
- 在邻接矩阵中的DFS与BFS的实现
- Leetcode 360 Sort Transformed Array
- vim安装插件YouCompleteMe,出错,YouCompleteMe unavailable: requires Vim compiled with Python 2.x support
- 编译防火墙——C++的Pimpl惯用法解析
- socket学习
- linux线程系列(5)线程清理和控制函数
- SVM简单代码实现MATLAB
- CS224d Assignment1 答案, Part(3/4)
- C++ 将当前系统时间转换成标准格式的时间和时间戳
- KEIL MDK 查看代码量、RAM使用情况--RO-data、RW-data、ZI-data的解释
- hdu 4571 Travel in time 最短路+dp
- C# 方法重载