pimpl完全的資訊隱藏

pimpl(pointer to implementation), 指向實作的指標。 這是《API Design for C++》Ch3.1的心得整理。
在此是要介紹,在C++中,如何實現「資訊隱藏狂熱」,class裡完全的將public以外的東西隱藏掉,在API設計中,這是很重要的,避免錯誤的使用,也讓設計更簡單好記。

在此,作者也有提到Effective C++ #34也有提及這個技巧。
無獨有偶的,Code Complete 2/e中也有提到。不過在《API Design for C++》中有強調,這是C++獨有的技巧,所以不算是通用的Design Pattern,不過,算是很厲害的Design Pattern for C++。

(在此,不使用書中的範例程式)
這個例子,是隱藏.cpp裡一切細節的範例程式的延伸版。
類別要描述的是「不會透露自己的年紀的人」。
透過pimpl技巧,強調「不會透露自己的年紀的人,更不會跟別人透露不想提及的意圖」。
//Person.h
#include <string>
#include "date.h"
#include "address.h"

class Person
{
    std::string theName;
    Date theBirthDate;
    Address theAddress;
    int GetYears(int currYear);
public:
    Person();
    ~Person();
    Person(const std::string& name,
           const Date& birthday,

    std::string name() const;
    std::string birthDate() const;
    std::string address() const;
    //...
};

極致的資訊隱藏手法

我們希望它可以只露出必要的部份,所以將它改寫成這樣
//Person.h
#include <string>
#include "date.h"
#include "address.h"

class Person
{
    class PersonImpl;   //如果使用上造成太多存取的限制,可以考慮將這一行改成public
    PersonImpl* pImpl;  //宣告一個實作類別的指標(或參考也行,就是不可以是實體)
public:
    Person();
    ~Person();
    Person(const std::string& name, 
           const Date& birthday,
           const Address& addr);

    std::string name() const;
    std::string birthDate() const;
    std::string address() const;
    //...
};
//Person.cpp
#include "Person.h"

class Person::PersonImpl
{
public:
    std::string theName;
    Date theBirthDate;    
    Address theAddress;

    int GetYears(int currYear)
    {
        return currYear - BirthDate.Year();
    }
};

Person::Person(const std::string& name, 
               const Date& birthday,
               const Address& addr):
pImpl(new PersonImpl())
{
    pImpl->theName = name;
    pImpl->theBirthDate = birthday;
    pImpl->theAddress = addr;
};

Person::~Person()
{
    delete pImpl;
    pImpl = 0;
}
除此之外,對於類別的複製建構式與賦值運算子的override都是必須要注意的實作細節唷。

使用Smart Pointer

書裡還建議使用smart pointer避免使用這種方式時,pimpl實作不見了的情況。
//Person.h
#include <personimpl>
#include "date.h"
#include "address.h"

class Person
{
    class PersonImpl;   //如果使用上造成太多存取的限制,可以考慮將這一行改成public
    std::unique_ptr pImpl;  //使用適合的Smart Pointer
public:
    Person();
    ~Person();
    explicit Person(const std::string& name, 
           const Date& birthday,
           const Address& addr);

    std::string name() const;
    std::string birthDate() const;
    std::string address() const;
    //...
};
  • shared_ptr 指向相同的物件,誤刪不消失。 
  • scoped_ptr 保證唯一,無法複製。

pImpl的優點

  • Information Hidding
  • 降低耦合
  • 加快編譯
  • ...

pImpl的缺點

  • 增加一點點的物件大小
  • 降低程式碼可讀性
  • 提高維護程本(除錯時要追程式碼就困難許多了)
  • 類別中的const函數,無法保證private的成員變數唯讀(只保證pImpl的指標不改變)

而這篇就是紀錄書中,如何改寫的注意事項。
詳細的細節,還是去看書吧!^^這本書很棒唷!

參考資料: 

[1] 3.1 Pimpl慣用語 - C++ API 設計 

沒有留言:

張貼留言

(什麼是留言欄訊息?)