PIMPL机制

总结:*PIMPL机制使类(目标类)的实现私用化,来降低编译依赖,提高重编译速度,保持类接口稳定性。

*PIMPL机制的实施方法是:在(类的头文件)声明目标类时,在类中添加私有成员:一个内部类(实现类)的指针,在目标类的实现文件(.cpp)中定义该内部类,并通过该内部类实现目标类的所有方法。以此将目标类头文件与目标类的实现隔离。(当除了接口改变外,目标类的内部实现改变,不会引起头文件的改动)。

PIMPL机制

转自:http://www.cnblogs.com/gnuhpc/archive/2012/06/30/2570761.html

作者:gnuhpc
出处:http://www.cnblogs.com/gnuhpc/

1.简介

这个机制是Private Implementation的缩写,我们常常听到诸如“不要改动你的公有接口”这样的建议,所以我们一般都会修改私有接口,但是这会导致包含该头文件的所有源文件都要重新编译,这会是个麻烦事儿。Pimpl机制,顾名思义,将实现私有化力图使得头文件对改变不透明

2.机制分析

首先,我们先看看不使用这个机制的一个实现:

  1: // MyBase.h
  2: class MyBase {
  3: public:
  4:   int foo();
  5: };
  6: 
  7: // MyDerived.h
  8: #include "MyBase.h"
  9: class MyDerived : public MyBase {
 10: public:
 11:   int bar();
 12: };


 

假设你现在希望在MyBase.h中加入一个新的private和protected成员函数,那么MyDerived和所有包含MyBase.h的源文件都需要重新编译。在一个大工程中,这样的修改可能导致重新编译时间的激增。你可以使用Doxygen或者SciTools看看头文件依赖。

一般来说,不在头文件中包含头文件是一个比较好的习惯,但是这也不能完全消除修改MyBase.h带来的重新编译代价。有没有一个机制可以使得对私有接口做修改时我们可以减小重新编译的代价。

在Pimpl机制中,我们使用前置声明一个Impl类,并将这个类的一个指针实例放入主类中,如下:

  1: // MyClass.h
  2: class MyClassImpl;    // forward declaration
  3: class MyClass {
  4: public:
  5:   MyClass();
  6: ~MyClass();
  7:   int foo();
  8: private:
  9:   MyClassImpl *m_pImpl;
 10: };


 

现在,除非我们修改MyClass的公有接口,否则这个头文件是不会被修改了。然后,我们用这个Impl类的实现来完成主类的细节实现,在主类的构造函数中,我们完成了实现类指针的实例化

  1: // MyClass.cpp
  2: class MyClassImpl {
  3: public:
  4: int foo() {
  5:         return bar();
  6: }
  7: int bar() { return var++; }
  8:         int var;
  9: };
 10: 
 11: MyClass::MyClass() : m_pImpl(new MyClassImpl){}
 12: 
 13: MyClass::~MyClass()
 14: {
 15:     try {
 16:             delete m_pImpl;
 17:     }
 18:     catch (...) {}
 19: }
 20: 
 21: int MyClass::foo(){ return m_pImpl->foo(); }


 

Pimpl机制其实这是桥接模式的一种变种。我们可以对实现类随意的进行增删和修改,而不会导致包含MyClass.h的源代码重新编译。当然,这样做的时间开销和空间开销也是有的。

在实践中,我们常常采用内部类来完成Pimpl机制:

  1: // header
  2: class fruit
  3: {
  4: public: 
  5: 
  6: private:
  7: class impl;
  8: impl* pimpl_;
  9: } 
 10: 
 11: // implementation
 12: class fruit::impl
 13: { 
 14: 
 15: }; 
 16: 
 17: fruit::fruit()
 18: {
 19: pimpl_ = new impl();
 20: }


 

 

PIMPL模式的实现和应用:

转自:http://www.cnblogs.com/kanego/archive/2011/11/23/2260700.html

看一些开源库,里面好多类有一个**IMPL。经查询还是有些门道和说法的。查询了一些相关资料。(英文没有翻译,挺简单的。)

PIMPL 也可以称为一种设计模式了。

现在摘录如下:

pimpl 手法在 C++ 里已是“高手”们广泛运用的成熟方法之一,它的优点很多,诸如降低编译依赖、提高重编译速度之类的工具性优势自不待赘言,而其对“保持接口稳定性”的优点更值得称道。

It makes it possible to avoid other classes to know internal data structures and other information of the class. It also simplifies some #includepreprocessor instructions.避免别的class知道其内部的数据结构。还可以简化预编译指令。

下面是一个Example WITHOUT PIMPL 。

如下:

File foo.h

 

 

class CFoo

{

public:

    CFoo();

    ~CFoo();

    bool ProcessFile(const CString & csFile);

private:

    CFooInternalData    m_data;

    CHeader             m_header;

} 

  

File foo.cpp

 

 

#include "FooInternalData.h"

 

#include "Header.h"

 

#include "foo.h"

 

CFoo::CFoo()

{

}

 

CFoo::~CFoo()

{

}

 

bool CFoo::ProcessFile(const CString & csFile)

{

    //do something

    return true;

}

 

Main File

 

#include "FooInternalData.h"

 

#include "Header.h"

 

#include "foo.h"

 

 

int main()

{

    CFoo foo;

    foo.ProcessFile("c:\\data.bin");

    return 0;

} 


 

The problem with this simple way of coding is that in your main file, you must include the foo.h to use it, but at the same time you must also include all needed files to allow the compiler to work correctly. In fact, the main does not need to include FooInternalData.h and Header.h (which are CFoo internal structures) except for compilation.... So with very big classes, you might do some huge includes and in this case, you can have some compiler or linker errors because files are already included elsewhere.

以上代码有些不足:

       第一,引入更多的头文件降低编译速度。而且这个声明当然写在一个头文件里,而头文件,是不能预编译或增量编译的,如果你因此而引入一个诸如<windows.h>之类的头文件,产生的代价可能是一杯咖啡的编译时间--而且每次编译都这样;
       第 二,大大提高的模块的耦合度。在这里,CFooInternalData从此与 CFoo紧紧绑定。在一个库里的模块互相耦合当然可以忍受,不过你要记得,这里有两种耦合度:一个是编译期的,一个是运行期的,这种方式下,无论编译还是运行,它 们都耦合在一起,只要 CFooInternalData 发生变更,CFoo 的模块也必须重新编译;
       第三,降低了接口的稳定程 度。接口的稳定,至少有两个方面:一个是对于库的运用,即方法调用不能变;一个是对于库的编译,即动态库的变更最好能让客户程序不用重编译。方法调用与这 个问题无关,但对于库的编译,如果CFooInternalData 变了,客户程序显然必须重新编译,因为 private 部分,虽然对于客户程序不可用,但并不是不可见,尤其是对编译器来说。对于一个动态链接库,这个问题可能会让人无法忍受。

pimpl 手法能比较完善的解决这些问题。利用 pimpl 手法,我们把数据细节隐藏到一个实现类里:CFoo_pimpl,而 CFoo 的 private 部分只剩下一个指针,那就是传说中的 pimpl 指针

The Same Example with PIMPL

File foo.h

 

 

//here just declare the class PIMPL to compile. 

//As I use this class with a pointer, I can use this declaration 

class CFoo_pimpl; 

  

class CFoo

{

public:

    CFoo();

    ~CFoo();

    bool ProcessFile(const CString & csFile);

private:

    std::auto_ptr<CFoo_pimpl>    m_pImpl;

}  

  

File foo.cpp

 

 

#include "FooInternalData.h"

 

#include "Header.h"

 

#include "foo.h"

 

 

//here defines PIMPl class, because it is use only in this file

class CFoo_pimpl()

{

public:

    CFoo_pimpl()

    {

    }

 

    ~CFoo_pimpl()

    {

    }  

    bool ProcessFile(const CString & csFile)

    {
        //do something
        return true;
    }

};

 

CFoo::CFoo()

:m_pImpl(new CFoo_pimpl())

{

}

 

CFoo::~CFoo()

{
    //do not have to delete, std::auto_ptr is very nice 
}

 

bool CFoo::ProcessFile(const CString & csFile)

{
    //just call your PIMPL function ;-)

   return m_pImpl->ProcessFile(csFile);
}

 
Main File

 
 

#include "foo.h"

 
int main() 

{

    CFoo foo;

    foo.ProcessFile("c:\\data.bin");

    return 0; 

} 


 

The result is obvious: simplicity of use!! The main does not need more includes for internal structures of CFoo class.

Thus it is an excellent optimization to minimize linker and compiler errors.

Conclusion

It is a very simple and nice way for good coding!!! If you want to use classes in other projects, it does not introduce including difficulties.

Unfortunately, you must add some more code to type.

代码来自:www.codeproject.com.

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值