前言

你是否总因头文件包含冲突而苦恼?
你是否因头文件包含错乱而苦恼?
你是否因封装暴露了数据而苦恼?
你是否因经常改动实现而导致重新编译而苦恼?

在这里, 这些问题都不是问题, 跟随作者, 揭秘pimpl.

正文

先来看一段例子:

有A, B 2个类, 分别由A.h, A.cpp, B.h, B.cpp文件实现.

同时, A类中包含了B类成员, B类中包含了A类成员.

 // A.h
 #include "B.h"
 class A {
 private:
     B b;
 };

 // B.h
 #include "A.h"
 class B {
 private:
     A a;
 };

你是否一眼就看出了问题!

是的, 头文件互相包含了, 那怎么解决此问题?

解决方案?

// A.h
class B;

class A {
public:
    A();
    ~A();
private:
    B *pB;
};

// A.cpp
#include "B.h"
A::A(): pB(new B())
{
}
A::~A()
{
    delete pB;
}

// B.h
class A;

class B {
public:
    B();
    ~B();
private:
    A *pA;
};

// B.cpp
#include "A.h"
B::B(): pA(new A())
{
}
B::~B()
{
    delete pA;
}

我们在头文件中互相前置声明, 这种行为是"不要钱"的, 大伙可以尽情的用.

随后在源文件中, 互相包含头文件, 在各自的构造函数中new 出对象, 析构函数中 delete 对象.

仅此而已?

接下来才是pimpl的重度解说.

假设我们的A需要实现成员函数 AmemberFunc.

这个成员函数过程复杂, 可能需要分割成多个小函数.

因此就有了一下清单:

func1();

func2();

...

funcN();

过程中还需要一系列成员变量.

因此就有了一下清单:

type1 var1, var2 ... varN;

type2 var1, var2 ... varN;

...

typeN var1, var2 ... varN;

注意, 你需要的仅仅是使用A的AmemberFunc接口.

其他的任何东西在你眼里都是累赘, 他们并不能让你更清楚实现, 反而扰乱你的思绪.

当你使用一个只有一个接口的对象时, 查看其头文件, 发现一堆的成员清单,

如果你胆子比较大, 或许会去源文件查看清单中的东西到底都在干什么.

这个时候的你已经走向歧途了.

当然, 也可能源文件已经被编译成了库文件, 你的壮志雄心不得已施展.

再次注意, 你需要的仅仅是这个接口, 至于接口的实现... 除非你的目的是把这个接口一探究竟, 否则纯属浪费时间.

废话说了不少, 看看解决方案.

// A.h
class A {
public:
    A();
    ~A();
    type memberFunc();
private:
    class impl;
    impl *pimpl;
};

// A.cpp
#include "B.h"
class A::impl {
    type memberFunc()
    {
        ...
    }
private:
    func1();
    func2();
    ...
    funcN();

    type1 var1, var2 ... varN;
    type2 var1, var2 ... varN;
    ...
    typeN var1, var2 ... varN;
};

A::A(): pimpl(new impl())
{

}

A::~A()
{
    delete pimpl;
}
type A::memberFunc()
{
    return pimpl->memberFunc();
}

A的内部声明了一个impl类型.

该类实例负责实现A的所有功能.

A只需调用impl相应的函数.

此方法隐藏了所有不需要暴露的细节.

并且避免了头文件的依赖. (因为实现在源文件中, 你可以在里面包含任意头文件不必考虑冲突问题.)

增强了可读性, 用户不会被"细节"扰乱思绪.

尾声

pimpl的功能远不止这些, 你可以在pimpl中抛出异常, 在外层类中捕获异常, 从而让异常完全透明.

还有更多功能, 等着你去发现..

当然, pimpl的缺陷也是显而易见的.

那就是不能内联! 不过谁会在意呢.

何为 pimpl ?的更多相关文章

  1. pImpl

    之前看代码,一直对pIml这个用法一知半解,参考这里 的一篇文章后有所收获. 总结一下,pIml的好处如下: 第一,引入更多的头文件降低编译速度.而且这个声明当然写在一个头文件里,而头文件,是不能预编 ...

  2. [linux]如何为Virtualbox虚拟硬盘扩容(转载)

    前言 这个教程介绍如何为Virtualbox虚拟硬盘扩容,虚拟硬盘分为动态分配大小和固定虚拟硬盘,扩容的方法不一样: 如何为动态分配的Virtualbox虚拟硬盘扩容 如何为固定大小的Virtualb ...

  3. AngularJS入门心得2——何为双向数据绑定

    前言:谁说Test工作比较轻松,最近在熟悉几个case,差点没疯.最近又是断断续续的看我的AngularJS,总觉得自己还是没有入门,可能是自己欠前端的东西太多了,看不了几行代码就有几个常用函数不熟悉 ...

  4. 何为“精通Java”

    何为精通Java?本来Java仅仅是一门语言,但从应用技术的角度来看,精通Java是可以无边无际的.很可能你可以对James说:我精通J2EE.JVM.Java服务器.大数据等等一些和Java相关的应 ...

  5. 如何为PHP贡献代码

    PHP在之前把源代码迁移到了git下管理, 同时也在github(https://github.com/php/php-src)上做了镜像, 这样一来, 就方便了更多的开发者为PHP来贡献代码. 今天 ...

  6. 如何为 Drupal 7 网站添加悬浮的反馈按钮?

    最近有客户咨询我们要怎么为 Drupal 网站添加悬浮按钮,方便访客能够链接到反馈表单页面.很幸运,使用 Feedback Simple 模块可以很容易实现. 在这篇短教程中,我将和大家分享如何添加链 ...

  7. 如何为Linux安装Go语言

    导读 Go 语言又称为 golang, 是由 Google 最初开发的一种开源编程语言,其在设计时就遵循了简单.安全和速度的 3 大原则.Go 语言具有多种调试.测试.分析和代码审查工具,如今 Go ...

  8. JS基础DOM篇之一:何为DOM?

    近日在园子看了一篇文章,一位前端负责人问应聘者何为DOM事件流的三个阶段,我当时一看也是懵圈,于是强迫症复发,遂想要搞清楚它.谁知在查资料的过程中发现有好多关于DOM的概念也是模糊不清,便决定继续延伸 ...

  9. [021]转 C++ Pimpl机制

    出处:http://www.cnblogs.com/gnuhpc/ 1.简介 这个机制是Private Implementation的缩写,我们常常听到诸如“不要改动你的公有接口”这样的建议,所以我们 ...

随机推荐

  1. JPA概要

    1 JPA概述 JPA(Java Persistence API,Java持久化API),定义了对象-关系映射(ORM)以及实体对象持久化的标准接口. JPA是JSR-220(EJB3.0)规范的一部 ...

  2. struts2框架通过jQuery实现AJAX应用

    众所周知,在web2.0时代,哪个web框架要是不跟AJAX沾点边,都不好意思说自己的框架有多么多么NB,当然struts也不例外,从 struts1开始到现在的struts2也都对AJAX有支持.A ...

  3. Struts2的声明式异常处理

    在struts2应用程序中你还在使用try catch语句来捕获异常么?如果是这样的,那你OUT啦!struts2支持声明式异常处理,可以再Action中直接抛出异常而交给struts2来 处理,当然 ...

  4. Linux下高并发网络编程

      Linux下高并发网络编程 1.修改用户进程可打开文件数限制 在Linux平台上,无论编写客户端程序还是服务端程序,在进行高并发TCP连接处理时, 最高的并发数量都要受到系统对用户单一进程同时可打 ...

  5. Linux企业级开发技术(3)——epoll企业级开发之epoll模型

    EPOLL事件有两种模型: Edge Triggered (ET)  边缘触发 只有数据到来,才触发,不管缓存区中是否还有数据. Level Triggered (LT)  水平触发 只要有数据都会触 ...

  6. [Locked] Inorder Successor in BST

    Inorder Successor in BST Given a binary search tree and a node in it, find the in-order successor of ...

  7. SwingConsole

    Java的Swing默认不是线程安全的,类的调度应该由线程分派器来安排.如果每个类都各个各的调度,有可能造成线程紊乱,带来一些难以检测的错误. 对于编写实验性代码(每次都只有一个JFrame),如果每 ...

  8. Theano 在windows下安装

    Theano + win8 一切为了 Deep Learning 选择安装方式:AnacondaCE  学术免费 Simply download and execute the installer f ...

  9. javaIO流小结(1)

    UTF-8的字节占多少个字节? 常用中文字符用utf-8编码占用3个字节(大约2万多字),超大字符集中要占4个字节.在内存中是2个字节,真正写到硬盘上面的是3个字节. GBK.GB2312汉字占2个字 ...

  10. 理解Android的startservice和bindservice(转)

    一.首先,让我们确认下什么是service? service就是android系统中的服务,它有这么几个特点:它无法与用户直接进行交互.它必须由用户或者其他程序显式的启动.它的优先级比较高,它比处于前 ...