Pimpl浅出

声明:我并没有很多废话,我喜欢简单精要,但是为了让大家看的更明白,就把书上的废话抄上了。只是把书本上的东西整理了下。

如果侵了谁的权,请告诉下,马上删除~哭。 突然发现上传的图片会自己打水印...........


Jeff Sumner最先引入了Pimpl这个术语,将其作为“Pointerto implementation”(指向实现的指针)的缩写。该技巧可以避免在头文件中暴露私有细节。因此它是促进API接口和实现保持完全分离的重要机制。但是Pimpl并不是严格意义上的设计模式,这种用法可以看做是桥接模式的一种特列。 

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

一.使用Pimpl

考虑一下下面的情况,假设有一个类A,它包含了成员变量b和c,类型分别为B和C,而如果D类要使用A类的话,那也变相的依赖了B和C,如下:

这个时候,D就要像下面这样去写:

虽然形式上是只需要include “A.h”,但是在链接程序的时候,却需要把B和C的模块也一并链接进去。

现在看看这样写的缺点:

1.      信息未隐藏:暴露了类A的底层细节,它是依赖对象b和c来实现部分功能。

2.      耦合度高:如果类B发生了变化,那么类A和类D需要重新编译。

3.      编译速度慢:引用层次较多的会减慢编译速度,特别是包含一些大型的库。

4.      二进制兼容:如果修改了B和C的数据成员,那么类D的大小会发生变化,对象的二进制会发生改变。

5.      非惰性分配:如果类D需要分配有限或高成本的资源,那么会导致启动速度变慢。

 

而Pimpl idiom就是这样的解决方案。所谓的Pimplidiom,就是声明一个类中类,然后再声明一个成员变量,类型是这个类中类的指针。用上面的例子来说明一下会清楚一下,代码如下:


有了上面的定义,那么D类就可以完全不用知道A类的细节,而且链接的时候也可以完全不用管B和C了。然后在A.cpp里面,我们就像下面这样去定义就好了:


优点就不说了,上面的缺点反过来看就是优点。

二.引出的问题

如果没有为类显示的定义复制构造函数和赋值操作符,C++编译器会默认创建。但是这种默认的构造函数只能执行对象的浅复制。这不利于采用Pimpl的类,因为这意味着如果客户复制了对象,则这两个对象将指向同一个Impl的实现对象。这样一来,这两个对象可能在析构函数中尝试删除同一个Impl对象,这很可能导致崩溃。处理这个问题有以下两种可选方案。

(1)      禁止复制类。如果不打算让用户创建对象的副本,那么可以将对象声明为不可复制的。可以通过显示声明复制构造函数和赋值操作符达到这一目的。不需要提供它的实现,仅声明就能防止编译器为其生成默认版本。将它们声明为私有也是一个好主意,这样在复制对象时将产生编译错误,而非链接错误。如果使用Boost库,只需继承boost::noncopyable即可。而新的C++0x标准允许完全禁用这些默认函数。

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

三.Pimpl与智能指针

Pimpl不方便且易犯错的一点是需要分配和销毁实现对象。无论何时,都有可能忘记在析构函数中删除对象,或者在Impl对象分配之前、销毁之后对其进行访问,从而引发错误。习惯上,应该确保构造函数做的第一件事就是分配Impl对象(最好通过其初始化列表),析构函数做的最后一件事应该是删除该对象。

         此外,可以借助智能指针简化上述操作,即采用共享指针或作用域指针维护实现对象的指针。由于作用域指针定义为不可复制的,如果使用这种智能指针声明不希望用户复制的对象,那就无需声明私有的复制构造函数和赋值操作符。m_Impl的定义可以为boost::scoped_ptr<Impl> m_Impl,

另外还可以使用boost::shared_ptr,它允许对象复制,但不会带来之前提到的二次删除问题。显然,使用共享指针意味着任何副本都将指向内存中相同的Impl对象。如果希望被复制对象拥有Impl对象的副本,那么仍然需要编写自己的复制构造函数和赋值操作符。

四.Pimpl的优点

²  信息隐藏:私有成员现在可以完全隐藏在公有接口之外,使得实现细节得以隐藏。信息隐藏也意味着公有头文件能够更加干净,更加清晰地表达真正的公有接口,因此也更易于用户阅读和理解。信息隐藏带来的另一个好处是,用户不能轻易的使用“脏”手段访问私有成员。例如向下面这样

                  #define private public

                  #define “yourapi.h”

                  #undef private

²  降低耦合:不用Pimpl,公有头文件就必须包含所有私有成员变量所需的头文件。这就增加了API与系统其他部分在编译时的耦合度。使用Pimpl可以将那些依赖项转义到.cpp文件中,并移除耦合的元素。

²  加速编译:将与实现相关的头文件移入.cpp文件带来的另一个隐含结果就API的引用层次得以降低,这将直接影响编译时间。

²  更好的二进制兼容性:采用Pimpl的对象大小从不改变,因为对象总是单个指针的大小。对私有成员变量做任何修改都只影响隐藏在.cpp文件内部的实现类的大小。如果对实现做出重大改变时,对象的二进制表示可以不变。

²  惰性分配:Pimpl类可以在需要时再构造,如果类需要分配有限的或高成本的资源(比如网络连接),惰性分配很有用。

五.Pimpl的缺点

         最主要的缺点是必须为你创建的每个对象分配并释放对象。这使对象增加了一个指针,同时因为必须通过指针间接访问所有成员变量,这种额外的调用层次与新增的new和delete开销类似,肯能引入性能冲击。

         此外,Pimpl也给开发人员带来了不便,即访问所有私有成员都需要增加形如m_Impl->前缀。增加的抽象层次使的实现代码更加难以阅读和调试。当Impl类拥有指回公有类的指针时,情况会更加复杂。你还必须记得为类定义复制构造函数或者禁止复制类。尽管如此,这些不便之处没有暴露给API的用户,因此就API设计而言不足为虑。它们是开发人员必须承担的负担,这样用户才能得到更加干净且高效的API。

六.参考文档:

1.C++ API 设计

2.http://airekans.github.io/cpp/2012/10/20/pimpl-idiom-in-c/


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值