#跟我学c++中级篇———pimpl

一、何方神圣——pimpl

Private Implementation,私有化实现。在c++中,由于语言本身的限制,没有纯粹的接口定义。这就导致了在接口的使用上很多c++的人员都是随心而动。有用抽象类的纯虚函数的,有直接用C类型的接口的。有干脆提供接口类的…不一而足吧。根据实际情况,实事求是的选择才是一个好的标准。
在c++中,大量的头文件的安全包含,本身就是一个重要的问题,普通的重复定义这都是小问题。一些莫名的,甚至头文件的顺序都引起的“血案”也是经常发生的。正所谓,代码量大了,啥情况都遇得到。而使用PIMPL机制,可以优化减少头文件中出现类似问题的风险。同时,在c++系统中还有一个重要问题,比如一个头文件被广泛包含,如果修改其中一些内容,可能导致大规模的代码重编译。但是如果采用PIMPL机制就会大幅降低这种情况,看下面的例子:

//a.h
class  Content
{
public:
  void Test();
private:
  int d;
};

//b.h
#include "a.h"
class UseContent1
{
public:
  void UseContentFunc();
private:
  Content c;
};

在上面的例子中,UseContent1可能会被上层更广泛的调用,这样,一旦Content增加了私有和保护这些本来不会影响对外接口的的代码时,仍然会引起上述的大规模的编译单元的重构。这个代价是很大的。但是,假如做一下下面的代码修改:

//b.h
//#include "a.h"
class Content;
class UseContent
{
public:
  void UseContentFunc();
private:
  Content* c;  //std::shared_ptr c;更好
};

你就会发现,在这个文件里,已经看不到Content的头文件了,这样,如果不对公有接口进行修改,就不会出现上述的情况。指针化,同时将实现移动到CPP文件中,就实现了降低耦合,提高编译速度,隐藏实现细节的好处。在小的工程里可能这样做的意义确实并不是多大,但是如果工程量大到编译需要小时以上时,他的意义就体现出来。
这也是软件编程原则里,提到的接口只能增加,不能修改的原因。因为如果只增加,老版本就可以正常使用,而新版本使用新接口,实现很好的版本兼容。
从本质上讲,PIMPL有些类似于设计模式中的桥接模式,也就是常说的,接口隔离变化。
头文件的递归包含,编译器类似“惊群”一样的重编译源码,都可以利用PIMPL进行有效安全的降低风险,当然,PIMPL也不是万能的,还是要根据情况来处理。

二、应用场景和注意点

主要的应用场景有下面三种:
1、代码解耦
这个很容易理解,通过指针来实现不同的类之间的互相隐藏,非常容易实现代码层次上的隔离,从而实现降低耦合。

2、降低编译依赖和头文件的嵌套层级,降低头文件应用风险

正如上面所述,减少了在头文件中,对其它头文件的包含,如此下去,就会有效的减少头文件的嵌套层级。同样,头文件的减少,就意味着编译的互相依赖性降低。大家在编程时,可能经常会遇到XXX.h无法发现的编译错误,完美的状态下,代码文件中除了自己的头文件不包含其它任何其它头文件,出了错也极其容易定位。

3、隔离接口变化,稳定接口实现版本兼容
外界只看到指针,而看不到指针具体实现,而具体的实现由接口来提供,这样,接口稳定,具体的指针实现可以灵活掌握。

使用pimpl,对于新手来说,一个最常见的问题就是“不完整类型定义”,很多新手在遇到这种问题,特别是代码量大的情况下,可能瞬间就懵了。在上面例子中,就可能隐藏着类似的问题。看下面的代码:

//b.cpp
#include "b.h"

void UseContent::UseContentFunc()
{
  c = new Content();
  c->Test();
}

这就会出现不完整类型定义?为什么出现呢。因为编译器看不到c,即Content这个类的定义。也就是说,编译器只知道有c,但不知道c里面到底有没有Test,不清楚。其实编译器都考虑不到Test就报错了,因为它还看不到c这个对象的构造函数。编译器就是这么傻,为啥看不到?因为没包含头文件,现在在上面的代码中增加头文件包含,就OK了。

//b.cpp
#include "a.h"
#include "b.h"

void UseContent::UseContentFunc()
{
  c->Test();
}

需要说明的是,pimpl的一个容易被人忽略的问题,那就是不同的编译器和不同的编译级别情况下,可能产生的问题有不同。这个就需要编程的实践经验了,但是其基本的原理都是上面所说。
另外需要注意的是,如果不使用智能指针,要注意指针的释放问题。

三、总结

通过上面分析可能看出来,pimpl的优势还是比较明显的,但是也不代表这种方式就是完美的,引入这个机制,必然增加设计的复杂性,相对来说降低了代码的易读性和维护性。这也是本文开头总是提到要实事求是,根据实际情况来引入各种设计的原因。既不能因噎食,更不能刻板教条。这其实是编程思想的一部分了。
#跟我学c++中级篇———pimpl_第1张图片

你可能感兴趣的:(C++11,C++)