Pimpl(pointer to implementation, 指向实现的指针)是一种常用的,用来对“类的接口与实现”进行解耦的方法。这个技巧可以避免在头文件中暴露私有细节(见下图1),因此是促进API接口与实现保持完全分离的重要机制。但是Pimpl并不是严格意义上的设计模式(它是受制于C++特定限制的变通方案),这种惯用法可以看作桥接设计模式的一种特例。

图1: Pimpl惯用法,这里的公有类拥有一个私有指针,该指针指向隐藏的实现类

在类中使用Pimpl惯用法,具有如下优点:

  • 降低耦合
  • 信息隐藏
  • 降低编译依赖,提高编译速度
  • 接口与实现分离

为了实现Pimpl,我们先来看一种普通的类的设计方法。 
假如我们要设计一书籍类Book,Book包含目录属性,并提供打印书籍信息的对外接口,Book设计如下:

class Book
{
public:void print();private:std::string  m_Contents;
};

Book的使用者只需要知道print()接口,便可以使用Book类,看起来一切都很美好。 
然而,当某一天,发现Book需要增加一标题属性,对Book类的修改如下:

class Book
{
public:void print();private:std::string  m_Contents;std::string  m_Title;
};

虽然使用print()接口仍然可以直接输出书籍的信息,但是Book类的使用者却不得不重新编译所有包含Book类头文件的代码。 
为了隐藏Book类的实现细节,实现接口与实现的真正分离,可以使用Pimpl方法。 
我们依然对Book类提供相同的接口,但Book类中不再包含原有的数据成员,其所有操作都由BookImpl类实现。

/* public.h */
#ifndef PUBLIC_H_INCLUDED
#define PUBLIC_H_INCLUDEDclass Book
{
public:Book();~Book();void print();private:class BookImpl;  // Book实现类的前置声明BookImpl* pimpl;
};#endif

在对外的头文件public.h中,只包含Book类的外部接口,将真正的实现细节被封装到BookImpl类。为了不对外暴露BookImpl类,将其声明为Book类的内嵌类,并声明为private。

BookImpl类的头文件如下。

/* private.h */
#ifndef PRIVATE_H_INCLUDED
#define PRIVATE_H_INCLUDED#include "public.h"
#include <iostream>class Book::BookImpl
{
public:void print();private:std::string  m_Contents;std::string  m_Title;
};#endif

private.h并不需要提供给Book类的使用者,因此,如果往后需要重新设计书籍类的属性,外界对此一无所知,从而保持接口的不变性,并减少了文件之间的编译依赖关系。


/* book.cpp */
#include "private.h"  // 我们需要调用BookImpl类的成员函数,// 所以要包含BookImpl的定义头文件
#include "public.h"  // 我们正在实现Book类,所以要包含Book类// 的头文件Book::Book()
{pimpl = new BookImpl();
}Book::~Book()
{delete pimpl;
}void Book::print()
{pimpl->print();
}/* BookImpl类的实现函数 */void Book::BookImpl::print()
{std::cout << "print from BookImpl" << std::endl;
}

使用Book类的接口的方法如下:


/* main.cpp */
#include "public.h"int main()
{Book book;book.print();return 0;

像Book类这样使用Pimpl的类,往往被称为handle class,BookImpl类作为实现类,被称为implementation class。

为简单实现起见,Book类省略了复制构造函数和复制赋值函数。在实际应用中,一般有两种可选方案解决Book的复制和赋值的语义问题。

(1) 禁止复制类 
如果不打算让用户创建对象的副本,那么可以将对象声明为不可复制的。可以将复制构造函数和复制赋值函数声明为私有的,这样在复制或者赋值时就会产生编译错误。 
以下代码通过声明私有的复制构造函数和复制赋值函数来使得对象不可以复制,不需要修改相关的.cpp文件。

/* public.h */
#ifndef PUBLIC_H_INCLUDED
#define PUBLIC_H_INCLUDEDclass Book
{
public:Book();~Book();void print();private:// 禁止复制类Book(const Book&);const Book &operator = (const Book &);class BookImpl;  // Book实现类的前置声明BookImpl* pimpl;
};

(2) 显示定义复制语义 
如果希望用户能够复制采用Pimpl的对象,就应该声明并定义自己的复制构造函数和复制赋值函数。它们可以执行对象的深复制,即创建对象的副本,而非复制指针。

Pimpl惯用法最主要的缺点是,必须为你创建的每个对象分配并释放实现对象,这使对象增加了一个指针,handle class成员函数的每次调用都必须通过implementation class,这会增加一层间接性。在实际中你需要对这些开销进行权衡。 
另外,采用了Pimpl的对象,编译器将不再能够捕获const方法中对成员变量的修改。这是由于成员变量现在存在于独立的对象中,编译器仅检查const方法中的pimpl指针是否发生变化,而不会检查pimpl指向的任何成员。

可以使用下图2来说明Pimpl方法在以上Book类设计的作用:

图2: Pimpl作为编译防火墙

由于Pimpl解除了接口与实现之间的耦合关系,从而降低文件间的编译依赖关系,Pimpl也因此常被称为“编译期防火墙“ 。

设计模式之Pimpl模式相关推荐

  1. C++设计模式之桥接模式

    这篇文章主要介绍了C++设计模式之桥接模式,本文讲解了什么是桥接模式.为什么要使用桥接模式.什么时候使用桥接模式等内容,需要的朋友可以参考下 问题描述 现在要去画一个图形,图形有长方形.圆形和扇形等等 ...

  2. java备忘录模式应用场景_图解Java设计模式之备忘录模式

    图解Java设计模式之备忘录模式 游戏角色状态恢复问题 游戏角色有攻击力和防御力,在大战Boss前保存自身的状态(攻击力和防御力),当大战Boss后攻击力和防御力下降,从备忘录对象恢复到大战前的状态. ...

  3. 一看就懂!【英雄联盟锐雯】与 Python 详解设计模式之门面模式

    [网络配图] 设计模式(Design Pattern)是一套被反复使用.多数人知晓的.经过分类的.代码设计经验的总结.使用设计模式的目的:为了代码可重用性.让代码更容易被他人理解.保证代码可靠性.设计 ...

  4. Python设计模式-装饰器模式

    Python设计模式-装饰器模式 代码基于3.5.2,代码如下; #coding:utf-8 #装饰器模式class Beverage():name = ""price = 0.0 ...

  5. Python设计模式-中介者模式

    Python设计模式-中介者模式 代码基于3.5.2,代码如下; #coding:utf-8 #中介者模式class colleague():mediator = Nonedef __init__(s ...

  6. Python设计模式-职责链模式

    Python设计模式-职责链模式 代码基于3.5.2,代码如下; #coding:utf-8 #职责链模式class Handler():def __init__(self):self.success ...

  7. Python设计模式-享元模式

    Python设计模式-享元模式 基于Python3.5.2,代码如下 #coding:utf-8class Coffee:name = ""price = 0def __init_ ...

  8. 建造者模式java_java设计模式3——建造者模式

    java设计模式3--建造者模式 1.建造者模式介绍: 建造者模式属于创建型模式,他提供了一种创建对象得最佳方式 定义: 将一个复杂对象的构建和与它的表示分离,使得同样的构建过程可以创建不同的表示 主 ...

  9. Java设计模式之策略模式与状态模式

    一.策略模式定义 定义:策略模式定义了一系列的算法,并将每一个算法封装起来,而且使他们之间可以相互替换,策略模式可以在不影响客户端的情况下发生变化. 好了,定义看看就完了,我知道你很烦看定义. 二.策 ...

最新文章

  1. 06. 为知笔记 -- 我的修改
  2. Keras: 多输入及混合数据输入的神经网络模型
  3. FineReport中以jws方式调用WebService数据源方案
  4. 【Python基础】Github标星4.7k,每天推送一个python小实例的Python库
  5. nginx upstream配置_Prometheus快速监控Nginx
  6. 【开源】NodeJS仿WebApi路由
  7. Internet History, Technology, and Security(week5)——Technology: Internets and Packets
  8. 爬虫-代理的质量控制
  9. Ngnix 安装、信号量、虚拟主机配置
  10. 方差分析与正交试验设计(四)
  11. Linkerd实战(2)示例详解
  12. c语言数组头尾交换逆序
  13. 计算机毕业设计 基于SSM的公交线路查询和管理系统
  14. 2021 计算机 保研经历 保研经验贴 保研知识扫盲 保研时间线(合肥工业大学 软件工程 rk4/165,211 3%)
  15. matlab最优控制实验报告_最优控制的MATLAB实现.doc
  16. 有限元计算 求解笔记(上)
  17. Xflow在泵阀产品设计中的应用
  18. 为什么都选择租服务器,服务器租用有什么好处
  19. php天气源码_PHP获取城市天气API接口源码
  20. ssh: connect to host ip地址 port 22: Connection refused (deepin适用)

热门文章

  1. 异步备份和还原数据库:.NET发现之旅(六)
  2. JQuery——选择器分类
  3. 2017-2018 ACM-ICPC, NEERC, Southern Subregional Contest
  4. Cocos2d-x 3.8.1+Cocos Studio 2.3.2捉虫记之控制场景文件中的骨骼动画
  5. Solr配置文件分析与验证
  6. 【强烈推荐】国土档案管理信息系统产品使用说明书系列目录【附下载地址】...
  7. 台湾MCI报告:Security SaaS风潮渐起
  8. 一篇文章让你了解智能合约以及和区块链的关系
  9. unity 灯笼_如何创建将自己拼成文字的漂亮灯笼
  10. 1106 Lowest Price in Supply Chain