另一個 Blog 地址:http://insaneguy.me
原文鏈接:http://insaneguy.me/2015/04/05/cheshire_cat_in_cpp-pimpl_idiom/
C++ 中的“柴郡貓技術”(Cheshire Cat Idiom),又稱爲 PIMPL(Pointer to IMPLementation) ,Opaque Pointer 等,是一種在類中只定義接口,而將私有數據成員封裝在另一個實現類中的慣用法。該方法主要是爲了隱藏類的數據以及減輕編譯時的壓力。
“柴郡貓”是什麼鬼?就是下面這貨:
柴郡貓(Cheshire cat)是英國作家劉易斯·卡羅爾(Lewis Carroll,1832-1898)創作的童話《愛麗絲漫遊奇境記(Alice’s Adventure in Wonderland)》中的虛構角色,形象是一隻咧着嘴笑的貓,擁有能憑空出現或消失的能力,甚至在它消失以後,它的笑容還掛在半空中。
– 來自百度百科
柴郡貓的能力和 PIMPL 的功能相一致,即雖然數據成員“消失”了(被隱藏了),但是我們的“柴郡貓”的笑容還是可以發揮威力。
下面通過例子來介紹一下 PIMPL 。
1 數據隱藏
C++ 中我們在頭文件中定義類,比如一個簡單的 Student 類由如下方式定義:
// student.h
class Student
{
public:
Student(); // Constructor
~Student(); // Destructor
void sayHello();
std::string getName() const;
void setName(std::string name);
int getAge() const;
void setAge(int age);
private:
string _name;
unsigned int _age;
};
這裏 _name
和 _age
是 Student
類的私有數據成員。然而使用該類的客戶往往更關心類的接口(該類能提供哪些服務),我們希望隱藏 Student
類的私有數據成員,這時候就可以利用 PIMPL 模式:定義一個實現類,將 Student
類的數據封裝到這個實現類中,同時在 Student
類中保留一個指向該實現類的指針變量。用代碼解釋更清楚:
// student.h
class Student
{
public:
Student(); // Constructor
~Student(); // Destructor
void sayHello();
std::string getName() const;
void setName(std::string name);
int getAge() const;
void setAge(int age);
private:
class CheshireCat; // Forward declaration
CheshireCat *_smileCat;
};
// student.cpp
#include "student.h"
#include <iostream>
#include <string>
using namespace std;
class Student::CheshireCat
{
public:
CheshireCat() :
_name(string("Guy")), _age(18) {}
~CheshireCat() {}
string _name;
int _age;
};
Student::Student() :
_smileCat(new CheshireCat())
{
}
Student::~Student()
{
delete _smileCat;
}
void Student::sayHello()
{
cout << "Hello! My name is " <<
_smileCat->_name << "." << endl;
cout << "I am " << _smileCat->_age <<
" years old." << endl;
}
string Student::getName()
{
return _smileCat->_name;
}
void Student::setName(string name)
{
_smileCat->_name = name;
}
int Student::getAge()
{
return _smileCat->_age;
}
void Student::setAge(int age)
{
_smileCat->_age = age;
}
好了,現在Student
類的接口沒有任何變化,但是頭文件中原有的私有數據成員消失了,只留下一隻微笑的柴郡貓(CheshireCat *_smileCat;
)。
2 節省編譯時間
使用 PIMPL 可以幫助我們節省程序編譯的時間。考慮下面這個類:
// A.h
#include "BigClass.h"
#include "VeryBigClass"
class A
{
//...
private:
BigClass big;
VeryBigClass veryBig;
};
我們知道C++中有頭文件(.h)和實現文件(.cpp),一旦頭文件發生變化,不管多小的變化,所有引用它的文件都必須重新編譯。對於一個很大的項目,C++一次編譯可能就會耗費大量的時間,如果代碼需要頻繁改動,那真的是不能忍。這裏如果我們把 BigClass big;
和 VeryBigClass veryBig;
利用 PIMPL 封裝到一個實現類中,就可以減少 A.h 的編譯依賴,起到減少編譯時間的效果:
// A.h
class A
{
public:
// 與原來相同的接口
private:
struct AImp;
AImp *pimpl;
};
3 副作用
使用 PIMPL 需要在堆空間上分配和釋放內存,內存開銷增加,同時也需要更多的間接指針跳轉,因此有一些副作用。
雖然如此,PIMPL 仍然是一種實現數據隱藏、減少編譯時間的有效方法。除非會引起顯著的程序性能下降,推薦使用 PIMPL 進行設計。
參考
http://stackoverflow.com/questions/60570/why-should-the-pimpl-idiom-be-used