柴郡貓技術--C++中的PIMPL設計模式

另一個 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_ageStudent 類的私有數據成員。然而使用該類的客戶往往更關心類的接口(該類能提供哪些服務),我們希望隱藏 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

http://en.wikipedia.org/wiki/Opaque_pointer

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章