C++的PIMPL模式解析

PIMPL(pointer to implementation)是一种常用的,用来对“类的接口与实现”进行解耦的方法。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_INCLUDEDclass Book{public: Book(); ~Book(); void print();private: class BookImpl; // Book实现类的前置声明 BookImpl* pimpl;};#endif

为简单实现起见,Book类省略了拷贝构造函数和拷贝赋值函数。在实际应用中,应该对这两个函数进行定义。 在对外的头文件public.h中,只包含Book类的外部接口,将真正的实现细节被封装到BookImpl类。为了不对外暴露BookImpl类,将其声明为Book类的内嵌类,并声明为private。

BookImpl类的头文件如下。

“public.h”#include <iostream>{public: void print();private: std::string m_Contents; std::string m_Title;};#endif

private.h并不需要提供给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(){Book book;book.print();return 0;}

像Book类这样使用pimpl的类,往往被称为handle class,BookImpl类作为实现类,被称为implementation class。 使用pimpl带来的额外开销包括,handle class成员函数的每次调用都必须通过implementation class,这会增加一层间接性,另外,每一个对象都增加了一个implementation class指针的大小。在实际中你需要对这些开销进行权衡。

可以使用下图来说明pimpl模式在以上Book类设计的作用:

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

本文的示例代码可以通过以下链接下载: 下载链接

参考资料

别人失去了信心,他却下决心实现自己的目标。

C++的PIMPL模式解析

相关文章:

你感兴趣的文章:

标签云: