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 */
#ifndef PUBLIC_H_INCLUDED
#define PUBLIC_H_INCLUDED
class Book
{
public:
Book();
~Book();
void print();
private:
class BookImpl; // Book實現類的前置聲明
BookImpl* pimpl;
};
#endif
為簡單實現起見,Book類省略了拷貝構造函數和拷貝賦值函數。在實際應用中,應該對這兩個函數進行定義。
在對外的頭文件public.h中,只包含Book類的外部接口,將真正的實現細節被封裝到BookImpl類。為了不對外暴露BookImpl類,將其聲明為Book類的內嵌類,並聲明為private。
BookImpl類的頭文件如下。
/* private.h */
#ifndef PRIVATE_H_INCLUDED
#define PRIVATE_H_INCLUDED
#include public.h
#include
class Book::BookImpl
{
public:
void print();
private:
std::string m_Contents;
std::string m_Title;
};
#endif
private.h並不需要提供給Book類的使用者,因此,如果往後需要重新設計書籍類的屬性,外界對此一無所知,從而保持接口的不變性,並減少了文件之間的編譯依賴關系。
/* book.cpp */
#include private.h // 我們需要調用BookImpl類的成員函數,
// 所以要包含BookImpl的定義頭文件
#include public.h // 我們正在實現Book類,所以要包含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.cpp */
#include public.h
int 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也因此常被稱為“編譯期防火牆“ 。