Friday, May 23, 2014

Multimethod (Double Dispatch), Ch11, Mondern C++ Design

首先,multimethod 對 C++ 來說是陌生的名詞,因為 C++ 沒有內建這樣的語法。最接近的 C++ 語法是 virtual member function,也就是針對一個多形的 reference/pointer,呼叫 virtual function 以後,實際執行的 function 要根據 reference/pointer 真正指到的物件實體來決定。方便起見,下面都只討論 reference,pointer 沒有什麼不同。

Double Dispatch

如果同時有兩個多形的 reference 要執行某個動作,而且實際執行的動作,需要「同時」根據「兩個 reference 各自的實體」來決定,那這個動作稱為 double dispatch,因為需要根據兩個物件來分配執行的動作。

上面的定義很抽象,書中舉了幾個例子,一個是在平面上,兩個 Shape 的重疊計算。Shape 有 Circle, Rectangle 等不同形狀的實作,如果要計算兩個 Shape 有沒有重疊,要先知道兩者確切的形狀,才能根據不同的形狀組合,用不同方式計算。

Shape 是二維的重疊,一維的重疊也有一樣的問題。像是電視預約錄影的時間表,有定時切台的 Reminder,有固定時段錄影的 RecordTimer,都繼承同一個 Event 結構。要檢查兩個 Event 有沒有重疊,也是把確切的 type 取出來,再針對三對不同 type 用不同方式計算重疊。

上面的兩個例子,兩個物件都是同一個多形繼承樹,而且運算有對稱。也可能是不同 class 的兩個物件,比如一筆抽象 Data 要給一個抽象 Device 來顯示,那就要根據具體 Data 和具體 Device 來決定實際的顯示方式。最後這個例子,可以很快的用兩組 virtual function 實作。既然是有兩個抽象物件,那就一次用一個 virtual 解出一個具體 type,經過兩次 virtual 就可以全部解出來。

class Device;
struct Data
{
    virtual void output2(Device*) = 0;
    virtual ~Data() {}
};

class Text;
class Image;
struct Device
{
    virtual void display(Text*) = 0;
    virtual void display(Image*) = 0;
    virtual ~Device() {}
};

struct Text : public Data
{
    virtual void output2(Device* dev)
    {
        dev->display(this);
    }
};
struct Image : public Data
{
    virtual void output2(Device* dev)
    {
        dev->display(this);
    }
};

struct Screen : public Device
{
    virtual void display(Text* text){} // Screen display Text
    virtual void display(Image* image){} // Screen display Image
};
struct Printer : public Device
{
    virtual void display(Text* text){} // Printer display Text
    virtual void display(Image* image){} // Printer display Image
};

上面的例子有些特點,值得仔細思考。
  1. 假設兩個物件分別有 M, N 個實際的型別,最多就需要 M*N 這麼多個 function 來處理不同組合。另一個極端是,如果可以把界面全部都拉到抽象型別,只依賴抽象界面,那就不需要 double dispatch。
  2. 抽象型別本身有 cyclic dependency,是需要 class name。具體的實作,則需要知道另一邊的繼承樹上面的所有實作,才能針對每個繼承樹上面的 class 作不同的操作。也就是說 Screen includes Text and Image,才能實作 display()。
  3. 上面的例子,把 output2() 換成 accept(),display() 換成 visit(),就完全跟 visitor pattern 一樣了。可以說 visitor pattern 是個 double dispatch 特例,某些情況也可以直接拿 Ch10 Visitor template 來用,只是 template 固定用 accept/visit() 可能使得 function name 不合乎實際功能。
  4. 這些 display() 裡面的運算,很可能完全不需要 Screen/Printer private 資訊,應該分離這段算法,使用其他 public 界面來操作 Screen/Printer。但是因為要得到 Device& 指到的具體型別,需要用 virtual function 做 dispatch。
既然 double dispatch 牽涉到 M+N 個 class,並且最多需要提供 M*N 個具體運算,那麼新增一個獨立的 class 來提供這樣的功能,也許會更好。

BasicFastDispatcher

首先,像上面的例子,利用 virtual function 可以解出一個 reference 指到的具體型別是什麼,比如每個 class 回傳一個獨特的 ID,之後就可以根據 ID 來判斷呼叫哪個 function。

下面這個 macro 是個聰明的作法,加到每一個 class。一開始 ID 都是 -1,但是回傳 reference,這樣就可以在註冊每一組 class 要執行的 callback 的同時,指定正確的獨特 ID。等到需要 dispatch 的時候,呼叫 virtual GetClassIndex() 就可以知道是什麼 class。

#define IMPLEMENT_INDEXABLE_CLASS(SomeClass)     \
static int& GetClassIndexStatic()                \
{                                                \
    static int index = -1;                       \
    return index;                                \
}                                                \
virtual int& GetClassIndex()                     \
{                                                \
    assert(typeid(*this) == typeid(SomeClass));  \
    return GetClassIndexStatic();                \
}

BasicDispatcher

除了加入 virtual function 以外,也可以使用 C++ typeid 運算子,來取得每個 class 獨特的 ID。這個作法跟上面的 BasicFastDispatcher 很像,註冊 callback 的時候用 typeid 紀錄每個 class 並且維護在 map 裡,執行時用 typeid 取得 reference 具體的 typeid,跟 map 比對就可以執行對應的 callback。

這個方式比上面的 BasicFastDispatcher 慢的原因,應該是 typeid 只有大小順序,所以需要在 map 裡面搜尋。而 BasicFastDispatcher 使用的 ID 從 0 開始連續定義,可以直接用 vector 不需要搜尋。但是 BasicDispatcher 不需要改動原本的 class,通常是較好的選擇。

C++11: 舊標準 operator typeid return class type_info。C++11 提供 type_index 把 type_info 包起來,但仍然只能比大小。

StaticDispatcher

這是使用一連串 if-dynamic_cast 來偵測 reference 具體型別的作法,也就是把所有可能的 class 都 dynamic_cast 過,如果有 cast 出來,那就是找到了。缺點是 dynamic_cast 是很花時間的動作,但是這種作法需要執行 M+N 次 dynamic_cast。

Summary

StaticDispatcher, BasicDispatcher, BasicFastDispatcher 書中有提供 template 實作,直接套用會比自己寫容易。如果是自己寫,常會在每個 object 塞一個 enum 用來區分 class,這就跟 BasicFastDispather 很接近。又或者一連串的 if-dynamic_cast,那就跟 StaticDispatcher 很像。這本書最後一章雖然很硬,但很實用。

另外,這章只有討論到兩個多形物件的 double dispatch,三個或者更多個物件作法類似,也是要先想辦法得到具體 class。更常用的可能是 double dispatch 加上額外的參數,比如兩個 Shape 邊緣相切算不算重疊,可能是重疊計算額外的參數。如果要套用書上的 template,就需要增加參數。

Monday, May 19, 2014

Visitor, Ch10, Modern C++ Design



這章的焦點焦點放在 visitor pattern 上面,因為對於這個 design pattern 不太熟,所以先來複習一下。

需求和動機

首先,在 visitor 架構中有一組多形的資料結構,需要支援一些操作。這應該很常見,像是線上商店可能會把書籍和音樂條列在一起,顯示的圖示不同,點選以後分別可以試閱或者試聽。因為要條列在一起,所以 Book & Music 要繼承同一個父類別,並且提供不同的 display() & preview() 實作。這樣使得資料和功能都寫在同一個 class,但這兩者應該分開,因為資料很少變動,可是功能經常修改或增減。

Visitor pattern 可以把功能從資料抽出來,把每個功能變成一個 Visitor class,而資料提供一個轉接的 virtual accept(Visitor&),只要傳入不同的 Visitor 就可以執行不同的功能。以線上商店的例子來說,Book::accept(DisplayVisitor) 會顯示書籍的圖示,而 Music::accept(PreviewVisitor) 會播放試聽的音樂。如果要修改書籍的圖示,單獨修改 DisplayVisitor 不會影響原有的資料,也不會影響 PreviewVisitor 的功能。

Cylic Dependency




上圖中,Visitee->AbstractVisitor->A 三者間有循環依賴的關係,雖然只是 depend by name,通常使用 forward declaration 就可以。問題是,如果增加一個 C 繼承 Visitee,並且針對 C 提供對應的 virtual AbstractVisitor::visit(C&),那麼所有繼承 Visitee 的 class 像是 A & B 都要重新編譯,因為AbstractVisitor 增加了新的 virtual function。

不管重新編譯的範圍有多大,可能都是很嚴重的問題,因為繼承的基本精神是「相同界面,不同實作」。上圖的 A & B 可能在兩個不同 library 裡面,根本看不到 source code,如果需要重新編譯,就是很大的 API change。

Acyclic Visitor



上圖把原本的 visit() 獨立出來,讓 A & B 各自使用各自的 Visitor,避免互相依賴。在實作裡, A::accept(AbstractVisitor& v) 因為參數提供的型別訊息不足,所以需要用 dynamic_cast<Visitor<A>&>(v) 以後才能呼叫 visit(*this)。當然,也是因為用更抽象的參數,才能避免 cyclic dependency。

比較兩張架構圖,可以發現 acyclic visitor 把型別檢查從編譯延遲到執行,以便得到 A & B 獨立的好處。假設 ConcreteVisitor 在新增 C 的時候沒有實作 Visit(C&),那麼 cyclic visitor 會 compile error,但 acyclic visitor 要到執行才會出錯。另外一個問題是 dynamic_cast 的效率,比起原來直接呼叫 virtual function 可能要多數十倍。

Summary

用 template 寫出 Visitor & Visitee 的骨架並不難,只要定義 pure virtual function 抽象界面。比較特別的地方有兩個:首先是 A & B 各自的 accept() 實作,每個要呼叫各自對應的 visit(),這章使用 macro 來示範,也可以用 curiously-recursive-template。另一個地方,是在決定 cyclic/acyclic 的時候盡可能減少 Visitee, A, B, ConcreteVisitor 的改動。這可以透過 typedef + template specialization 達成,只需修改兩個 headers,其他的實作都只要重新編譯就可以了。

Monday, May 12, 2014

Abstract Factory, Ch9, Modern C++ Design

在 design pattern 裡面,abstract factory 跟 factory 不太好區分,我先間單介紹一下差別。首先,factory 的產品只有一個,所以只提供一個 B* create(),實際回傳的產品,可能是 B1*, B2*, 或者 B3*,但是 B1, B2, B3 都是繼承 B。相較之下,abstract factory 的產品是一組 A, B, C,所以也提供一組函式,

virtual A* createA();
virtual B* createB();
virtual C* createC();

因為 A, B, C 各自有自己的子類 A1/2/3, B1/2/3, C1/2/3 等,而且存在某條件限制 A1, B1, C1 一定要同時搭配使用,所以實作 Factory1 固定只會製造 A1, B1, C1,就可以在產生 Factory1 的同時保證「A1, B1, C1 一定搭配使用」。如果需要換成 A2, B2, C2 的組合,把 Factory1 換成 Factory2 就可以了。

也就是說,factory 跟 abstract factory 的產品不同,選擇要製造哪一種產品的時間也不同。Factory 是製造的產品的時候選擇,但是 abstract factory 在建立 factory 的時候,就已經決定要製造哪種產品。

這樣可以回答一個問題:能否把多個 factory 合併成一個 abstract factory?因為選擇產品的時間不同,所以本質就不同,沒辦法合併。考慮極端的情況,abstract factory 可能只有一種產品 A,也還是跟 factory 不同。另外,前一章的 template Factory 其實有個必須修改產品的缺點,雖然可以從外部改,但是每個產品都要改。這章的 template AbstractFactory 就完全不需要改產品,是不錯的設計。

這章使用的範例是遊戲裡面的敵人,分成 Soldier/Monster 不同類,遊戲本身又分成 easy/hard 兩種模式,所以在 Easy 模式下面,SillySoldier/SillyMonster 會一起出現。套用 abstract factory 的方式,可以在選擇難度的時候產生 EasyEnemyFactory,使得之後的遊戲一定會產生 SillySoldier/SillyMonster。

Summary

在 abstract factory 裡面,有兩個不同的 class,一個是 AbstractFactory 提供製造的界面,另一個是 ConcreteFactory 繼承 AbstractFactory 並提供製造的實作。因此必須定義這兩個 template class。AbstractFactory 的參數是產品的抽象型別,用 Typelist 輸入,就會產生所有 virtual Type* create<AbstractProduct>() 的界面。

typedef AbstractFactory<TYPELIST_2(Soldier, Monster)> AbstractEnemyFactory;
typedef ConcreteFactory<
    AbstractEnemyFactory,
    TYPELIST_2(SillySoldier, SillyMonster)>
    EasyEnemyFactory;
AbstractEnemyFactory* pFactory = new EasyEnemyFactory;
Soldier* pSoldier = pFactory->create<Soldier>();
pSoldier->speak();

至於 ConcreteFactory 的參數,就要給相對的 AbstractFactory 來繼承,並且給具體的產品型別,才能夠製造正確的產品。

實作的部份,AbstractFactory 使用 GenScatterHierarchy 多重繼承得到 create<A>(), create<B>() 等抽象界面,然後 ConcreteFactory 使用 GenLinearHierarchy 線性繼承來實作 create<A>(), create<B>() 這些具體實作。要特別注意,繼承的時候同名的 function 會有 hiding 的問題,但如果是 override 父類別的 virtual function 則沒有問題。

書中提到另外一種典型的 AbstractFactory 實作 PrototypeFactory。一樣是繼承 AbstractFactory 並且實作 create<T>,但是 Prototype 使用 clone() 來製造新的產品。既然是 clone() 就需要增加 member function setPrototype(),但是 C++ 繼承結構裡面有同名 member hiding 的問題,書中使用 friend global function 來避免。

更好的方法是用 cast 解決 hiding,這也是 C++ hiding 機制的標準解法。要取得 recursive template 的真正型別有點困難,需要 AbstractFactory 提供正確的 typedef。

public:
    // template wrapper function only instanciates in the most derived class
    template<class T>
    void setPrototype(T* pProto)
    {
        // calling hiding member function by the exact class name
        this->BaseCloneUnit<T>::Result::doSetPrototype(pProto);
    }
    // member function that is hiding by the most derived class
    void doSetPrototype(AbstractProduct* pProto)
    {
        m_prototype = pProto;
    }

protected:
    // this template depends on AbstractProductList and AbstractFactoryType
    // in AbstractFactory
    template<class T>
    struct BaseCloneUnit
    {
        typedef typename Tail<typename Reverse<
            typename Base::AbstractProductList>::Result, T>::Result TL;
        typedef CloneUnit<T, GenLinearHierarchy<
            typename TL::Tail,
            CloneUnit,
            typename Base::AbstractFactoryType> > Result;
    };

private:
    // member data that can be accessed in member function
    AbstractProduct* m_prototype;

使用結果如下,注意 setPrototype<Monster>() 要指定抽象產品,不然沒辦法轉出正確的型別。因為不是同一個 class overloading,所以必需要區分。

typedef AbstractFactory<TYPELIST_2(Soldier, Monster)> AbstractEnemyFactory;
typedef ConcreteFactory<AbstractEnemyFactory,
    TYPELIST_2(Soldier, Monster), CloneUnit> PrototypeEasyEnemyFactory;
PrototypeEasyEnemyFactory* pPrototypeFactory = new PrototypeEasyEnemyFactory;
AbstractEnemyFactory* pFactory = pPrototypeFactory;
pPrototypeFactory->setPrototype<Monster>(new SillyMonster);
pPrototypeFactory->setPrototype<Soldier>(new SillySoldier);
Soldier* pSoldier = pFactory->create<Soldier>();

狂想:如果用 C++11 variable template argument 或者 proprocessor macro 也許就不需要 Typelist + GenXXXHierarchy 這麼複雜的組合了。

Tuesday, May 06, 2014

Object Factories, Ch8, Modern C++ Design

這章是從 Factory Pattern 出發的,也稍微解在什麼情形需要用 Factory Pattern。使用 Factory Pattern 的時候,常會出現固定的模式,只有 type 不一樣,但 code flow 很相似的情況,像是下面的例子。
class Fruit{};
class Apple : public Fruit{};
class Orange : public Fruit{};
class Grape : public Fruit{};

class FruitFactory
{
public:
    enum EN_FRUIT_TYPE
    {
        E_APPLE,
        E_ORANGE,
        E_GRAPE,
    };

    static FruitFactory* getInstance(){...}

    Fruit* create(EN_FRUIT_TYPE enType)
    {
        switch(enType)
        {
            case E_APPLE:
                return new Apple();
            case E_ORANGE:
                return new Orange();
            case E_GRAPE:
                return new Grape();
        }
    }
};
...
Fruit* foo = FruitFactory::getInstance()->create(FruitFactory::E_APPLE);

上面的例子裡,最明顯的特徵就是有個 create(),根據參數透過 switch 來決定要產生哪個實體。我個人每次寫這段的時候,都要檢查再三,確定 E_APPLE 真的是對應 new Apple(),因為每個 case 都很類似,所以複製貼上再修改,很怕眼花改漏改錯。我想,很多書上不建議用 switch 這是其中一個理由。

像這樣重複的 code flow 的確是 template 的好戲。這章的作法,是把 Factory 寫成 class template,並且把 switch-case 用一個 map 取代。如此一來,需要 Factory class 的時候,只要套用 template 就可。要注意,map 跟 switch 的本質不同,map 是個資料結構,是一個變數,要像switch 一樣到處都可以呼叫,就要是 global 的。也就是說,這裡假設 Factory class 一定是 singleton 或者 mono-state。像上面的 FruitFactory::create() 沒有使用 local 變數,很容易滿足這個條件,這也是最常見的情況。

template<
    class ProductType,
    typename IdType,
    typename Creator = Fruit* (*)()>
class Factory
{
public:
    static Factory* getInstance()
    {
        static Factory s_instance;
        return &s_instance;
    }

    bool registerType(IdType id, Creator creator)
    {
        m_map[id] = creator;
        return true;
    }
    ProductType* create(IdType id)
    {
        if(m_map.end() == m_map.find(id))
        {
            return NULL;
        }
        return m_map[id]();
    }

private:
    std::map<IdType, Creator> m_map;
};
...
typedef Factory<Fruit, int> FruitFactory2;

Factory template 必要的參數是 ProductType,然後使用之前要先把 id-creator 的對應關係註冊,而且要在整個程式最開始就註冊,所以要用 static 的方式呼叫。下面是每個要支援的 type 都要註冊。

class Apple : public Fruit
{
    static bool m_bRegistered;
public:
    enum
    {
        ID = 1
    };
    static Fruit* create()
    {
        return new Apple();
    }
};
...
bool Apple::m_bRegistered = FruitFactory2::getInstance()->registerType(Apple::ID, Apple::create);

最後,使用方式跟原本一樣,呼叫 creator 並且指定 id 就可以了。

Fruit* bar = FruitFactory2::getInstance()->create(Apple::ID);

用 Factory template 主要的好處有兩個。第一,每次用到 Factory,只要 typedef 就可以產生一個新的 Factory。第二,同一個 Factory,要加入新的 type 的時候,不必改 Factory 本身,用 add 取代 modify,不會影響舊的功能。

幾個細節問題討論如下。
  1. ID 如何決定則是一個比較傷腦筋的問題,因為 C++ 沒有提供固定的 type id,所以一般是用 hard code 使用字串或者亂數作為索引。甚至,用古老的方式寫 enum 集中管理也是個方式。
  2. 錯誤處理的部份,可能發生 map 找不到的問題,也就是 switch 裡面的 default case,用 policy class 可以選擇不同處理方式。
  3. 最後一個觀察,是 singleton 的部份。前面提到 map 只能有一份,而且 id-creator 必須在程式一開始註冊,所以使用 singleton 是很合理的。這章直接套用 Singleton template 來完成,可以在不同地方提供 Factory 不同的 Singleton 實作,非常的有彈性。

Wednesday, April 30, 2014

Smart Pointers, Ch7, Modern C++ Design

Smart pointer 對 C++ 來說,是很常用的東西。使用 smart pointer 主要的目的,是 "resource allocation is initialization (RAII)",這樣有能在 resource 不需要用的時候,藉由 smart pointer 自動 delete,省去一些管理 pointer 的困難。

這章提到的 SmartPtr 設計細節很多,主要是使用上的需要支援許多功能,卻又需要考慮效能和維持易用的語法,所以有些地方需要取捨。這些地方,都是設計 library 的時候常常碰到的問題,是很好的範例。

Smart Pointers 101


Smart Pointer 主要的用法,就是除了宣告以外,其他地方要用起來跟 raw pointer 一樣,因此需要實作下面的 member function:

  1. explicit SmartPtr(T* pointee)
  2. SmartPtr& operator=(const SmartPtr& other)
  3. ~SmartPtr()
  4. T& operator*() const
  5. T* operator->() const

除了 operator*() 和 operator->(),還有其他 operator 是 raw pointer 支援的,但是 SmartPtr 考量使用情況不支援的:

  1. T** operator&(): 可以提供更多的 raw pointer 相容性,方便直接以 SmartPtr 替換 raw pointer。但是這會曝露裡面的指標,而且會讓 STL container 等 template 用來取址的時候出錯。
  2. Implicit Conversion Operator (operator T*()): 可以提供更多相容性,但是很容易被誤用,像是 delete spFoo 就可以自動轉型。這對於 raw pointer 直接把宣告換成 SmartPtr 的 code 非常容易出錯。
  3. Array operator[](): 應該盡量使用 std::vector,所以不支援 array。實際上可以用修改 StoragePolicy 的方式,在 destructor 呼叫對應的 delete[] 達成指向 array 的目的。要使用 operator[] 或者指標加減,需要額外取得實際的指標。
  4. 指標的 ordering comparisons,SmartPtr 預設也沒有提供。SmartPtr 指到的位址通常是分別配置的,比較大小沒有意義。Effective C++ 也有提到,避免直接使用指標運算加減,同一個陣列應該用 pArray[index] 的寫法。

另外,operator==, operator!=, operator! 的部份,raw pointer 很常用,必需要支援。直接用 operator bool() 自動轉型有很多問題,像是兩個不同型的指標可以都自動轉成 bool 去比較,或者自動轉成 bool 跟其他整數型別比較。總之,C++ 自動轉型很容易出問題,像是 std::string 也是額外提供 c_str() 而不是自動轉型。因為沒有自動轉型,所以這些 operator 必須一個一個定義出來,包括 SmartPtr 跟 raw pointer 的排列組合。

SmartPtr Member Functions


除了 operator overload 為了讓 SmartPtr 用起來跟一般指標相容,SmartPtr 不提供 member function,因為容易跟 pointee 的 member function 搞渾。像是下面的 release,改成 global 比較明顯。

struct Foo{ void release(); };
SmartPtr<foo> spFoo;
spFoo->release();
spFoo.release(); // 跟上面搞混了
release(spFoo); // 明顯不是呼叫 Foo

Multithreaded Reference Linking


因為使用環狀的 double-linked list,恰好兩個 SmartPtr 物件的情況,prev_ == next_ 對兩個物件都成立。下面這節處理 linked-list 的 code 需要改進,才能處理兩個物件的情況。修改以後,恰好兩個物件的時候,走 else 的分支,也會形成正確的 circular double-linked list.

//7.13.2.2 Multithreaded Reference Linking, p. 164
~SmartPtr()
{
    if ((prev_ == next_) && (prev_ == this))
    {
        delete pointee_;
    }
    else
    {
        prev_->next_ = next_;
        next_->prev_ = prev_;
    }
}

這裡還有另外一個觀察,SmartPtr 維護 book-keeping data 的 thread safe 的重要性。Book-keeping 只有在 construct, assign, destruct 這三個地方發生,相對於 dereference 算是很少的。個人經驗上,通常是某 thread 頻繁使用某變數,而其他任何一個 thread 偶爾用到同一個變數,這種情況比較容易發生 race-condition。如果 SmartPtr 沒有頻繁複製的話,應該不太需要擔心 thread safety。這也可以解釋 reference-linking 不處理 thread 問題,因為要額外處理 mutex lock 太花成本。相比之下,reference-counting 只要 atomic increment 就達成 thread safe,相對來說快很多。


Storage Policy Revisited


讀到 7.14.1 The Storage Policy 的時候,突然想到 C code 常有下面這樣的 API 設計:

file_id fid = file_open("aaa.txt");
file_read(fid);
…
file_close(fid);

像是 file_id 這樣的 handle,作用跟指標類似,也需要資源管理。這樣的 handle 可以用 SmartPtr 來操作嗎?測試以後確定,只要適當的使用 StoragePolicy,定義好 StoredType 就可以用 SmartPtr 來管理這種 handle,但是只能管理 ownership,不能用 operator->() 或者 operator*(),因為無法對應相關的 global function。如此使用上需要 GetImp(ptrHandle),這是先天的限制,因為 handle 或 id 常常就是為了不直接使用 pointer 的刻意設計。

心得:實用案例


假設有某個 Task 需要二個不同 resource 才能運作,

struct Task
{
    SmartPtr<R1> pR1;
    SmartPtr<R2> pR2;
};

在 StartTask() 的時候,需要取得每一個 resource,每個都取得才會成功。

bool StartTask()
{
    Task task;
    if(NULL == (task.pR1 = getResource1()))
    {
        return false;
    }
    if(NULL == (task.pR2 = getResource2()))
    {
        return false; // 自動釋放 task.pR1
    }
    m_runningTask = task; // 成功了,保存這個 Task 的狀態
    return true;
}

如此這段邏輯就很清楚,萬一中間多插了一個 return,或是發生 exception,也可以正確釋放資源。這個例子裡面,SmartPtr 使用 ownership transfer 就夠用了。如果其他地方有用到,reference counting 就比較適合。

Thursday, April 17, 2014

Functor, Ch5, Modern C++ Design

從這章開始,這本書使用 template 等技巧實作了 Loki library 的主要元件,也就是 design pattern 的功能。這章 Functor 的設計,是要讓 library 使用者快速套用 Command Pattern。

Command Pattern

這是 Command Pattern 的架構,其實在很多跟 UI 有關的 Framework 都使用了這樣的設計。
使用的時候,client 自訂 ConcreteCommand 要呼叫的 Receiver::Action 和參數,然後把這個物件傳給 Invoker,這樣就能在需要的時間點,讓 Invoker 執行該做的事。

因為通常是 Receiver 真正做事, Command & ConcreteCommand 只存參數等狀態,所以 Command & ConcreteCommand 常常只是 forwarding,於是這章打算把 forwarding command 包裝起來,不用每次都重複寫。包裝起來需要做到下面的功能:
  1. Create 的時候,要能設定 Receiver, action, 還有 action 的參數。
    1. Command::Execute() 可能有不同的 return type, parameter type,每一組 prototype 都是一個 class,所以要用 class template 來做。
    2. Create 的時候,可以給已知的參數,這樣 prototype 就會改變。
    3. 可以產生 MacroCommand,可以一次執行多的 ConcreteCommand.
  2. 可以被 Invoker 執行,並且呼叫正確的 action.
  3. C++ 的 callable entity 都要能使用。
    1. Function pointer
    2. Functor (class with operator())
    3. Pointer to member function
  4. 因為 Command + Execute() 本身就跟 functor 結構一樣,而且這邊設計的萬能 Command 需要同時能夠呼叫 functor 和其他 Command,就統一使用 operator() 代替 Execute,並且把 class 命名為 Functor。(要自動判斷呼叫 operator() 還是 Execute(),應該很難設計)

    Functor

    整理一下上面的需求,可以得到下面的功能列表。
    1. Functor 的 prototype (return type + parameter type) 是 template 分類的方式。
    2. 因為 parameter type 長度不固定,用 Typelist 來處理。
    3. Constructor 有三種,分別負責記住 function pointer, functor, pointer to member function.
    4. Overload operator() 來執行 constructor 給的參數。
    5. 內部處理不同 constructor 的資料,因為資料不一樣大,執行方式也不同,用 private class 繼承。
    假設 Functor 最多吃兩個參數,一開始寫出來的 code 大概像這樣,可以滿足幾個重點。第一,只要 prototype 固定, Functor 就是個完整的 type,這對 library 設計有很有用。第二,Typelist 可以處理不同長度的 prototype。第三,function pointer 和 functor 都能用,只要 constructor 給參數。第四,operator() 有不同長度的版本,利用 template 沒用到就沒實體化的特性。第五,因為 functor 等資料大小不同,用 private class 動態產生不同大小或型別的容器來存。

    template<typename RType, class TL>
    class Functor
    {
        typedef typename IndexOf<TL, 0>::Result P1;
        typedef typename IndexOf<TL, 1>::Result P2;
    public:
        template<typename Ftor>
        Functor(Ftor ftor)
        {
            m_pImpl = new FunctorImpl<RType, TL, Ftor>(ftor);
        }
        RType operator()()
        {
            return (*m_pImpl)();
        }
        RType operator()(P1 p1)
        {
            return (*m_pImpl)(p1);
        }
        RType operator()(P1 p1, P2 p2)
        {
            return (*m_pImpl)(p1, p2);
        }
    
    private:
        template<typename RType2, class TL2>
        class BaseImpl
        {
            …
        };
    
        template<typename RType2, class TL2, typename Ftor>
        class FunctorImpl : public BaseImpl<RType2, TL2>
        {
            ...
        };
    
        BaseImpl<RType, TL>* m_pImpl;
    };
    

    在實作 BaseImpl 的時候,會想跟 Functor 本身一樣提供不同長度的 operator(),並且給子類別 virtual 實作,但是這樣不行。BaseImpl::operator() 如果是 virtual,就一定要在 template 裡面實體化,因為 virtual 沒辦法事先判斷有沒有用到。如果又同時 overload,就會讓所有版本實體化,造成參數數量錯誤的 compile error。

    所以實作的方式,是用 template specialization 根據參數長度,讓每個長度的 operator() 都放在分別的 BaseImpl 裡面,這樣就可以宣告為 virtual。然後實作的 FunctorImpl 再 overload 所有長度的 operator(),符合長度的那個是繼承的 virtual 一定會實體化。其他的長度不是 virtual,不會實體化可以 compile,如果傳錯參數長度,實體化的時候也會 compile error。

    template<typename RType2, class TL2> class BaseImpl;
    template<typename R>
    class BaseImpl<R, TYPELIST_0()>
    {
    public:
        virtual R operator()() = 0;
        virtual ~BaseImpl() {}
    };
    template<typename R, typename P1>
    class BaseImpl<R, TYPELIST_1(P1)>
    {
    public:
        virtual R operator()(P1 p1) = 0;
        virtual ~BaseImpl() {}
    };
    template<typename R, typename P1, typename P2>
    class BaseImpl<R, TYPELIST_2(P1, P2)>
    {
    public:
        virtual R operator()(P1 p1, P2 p2) = 0;
        virtual ~BaseImpl() {}
    };
    
    template<typename RType2, class TL2, typename Ftor>
    class FunctorImpl : public BaseImpl<RType2, TL2>
    {
    public:
        FunctorImpl(Ftor ftor) : m_ftor(ftor) {}
        RType2 operator()()
        {
            return m_ftor();
        }
        RType2 operator()(P1 p1)
        {
            return m_ftor(p1);
        }
        RType2 operator()(P1 p1, P2 p2)
        {
            return m_ftor(p1, p2);
        }
    
    private:
        Ftor m_ftor;
    };
    

    這段是很巧妙的設計,靠著 template specialization 可以讓 virtual function 選擇性的實體化。

    Pointers to Member Function


    上面的實作,可以同時支援 pointer to function & functor。根據上面的實作,寫出 pointer to member function 是很容易的,只要在 constructor 提供兩個參數,pointer to object & pointer to member function 就可以了。
    public:
        template<class ObjPtr, class MemberPtr>
        Functor(ObjPtr objPtr, MemberPtr memberPtr)
        {
            m_pImpl = new PointerToMemberImpl<RType, TL, ObjPtr, MemberPtr>(objPtr, memberPtr);
        }
    …
    private:
        template<typename RType2, class TL2, typename ObjPtr, typename MemberPtr>
        class PointerToMemberImpl : public BaseImpl<RType2, TL2>
        {
        public:
            PointerToMemberImpl(ObjPtr objPtr, MemberPtr memberPtr) :
                m_objPtr(objPtr),
                m_memberPtr(memberPtr)
            {}
            RType2 operator()()
            {
                return (m_objPtr->*m_memberPtr)();
            }
            RType2 operator()(P1 p1)
            {
                return (m_objPtr->*m_memberPtr)(p1);
            }
            RType2 operator()(P1 p1, P2 p2)
            {
                return (m_objPtr->*m_memberPtr)(p1, p2);
            }
    
        private:
            ObjPtr m_objPtr;
            MemberPtr m_memberPtr;
        };
    


    注意 Functor constructor 的第一個參數是 pointer to object,語意可以明確表示,建構 Functor 的時候和執行的時候,都是用同一個 object。雖然 pointer to member function 比較晚介紹,但是可能是最常用的,因為 C++ 通常 member function 佔多數。

    Binding and Chaining Requests

    Functor (也就是 command pattern)有兩個常見的特殊應用,第一個是 binding,create 的同時給部份參數特定的值,但是剩餘的參數執行時才給。如此一來,執行時的 prototype 會改變。最常見的特例,就是 command pattern 建立 command 的同時,寫好幾個 setXXX() 把所有參數指定,使得執行變成 void execute(void)。

    Chaining 則是把好幾個 Functor 串成一個,也就是 command pattern 的 macro command。要注意 chaining 通常是同樣的 prototype 才能組合,這樣參數才能一致,return 也就可以選擇某一個。最常見的 chaining 通常也是 void execute(void) 這組 prototype,沒有參數和 return 的模糊。

    這兩個實作都很直接使用 template。

    Friday, April 11, 2014

    Small-Object Allocation, Ch4, Modern C++ Design

    這章算是開始介紹 library 的設計,為後面其他工具鋪路,要設計一個有效率的 allocator。個人對於 memory 的配置不特別感興趣,因為這是 C++ 很基本的功能,也有很多 library 針對不同需求實作這個東西,直接找來用是很簡單的。但是除了 memory,其他地方也是有類似的資源分配需求,這章有些技巧可以學。



    上面的圖表示了四層的設計。SmallObject 是對外的界面,只要繼承他就可以快速 new-delete。SmallObjAllocator 則是負責 allocate 不同大小的 memory。FixedAllocator 只負責處理固定大小的 allocation。Chunk 則是把小塊 memory 拼成大塊,轉交給底層 ::operator new 一次處理一大塊。

    Chunks

    這是 Chunk 的定義,用 Init() 來真的呼叫 ::operator new 取得一塊大塊 memory,用 pData_ 記下來。書裡沒列出的 Deinit() 應該要呼叫 ::operator delete。

    struct Chunk
    {
        void Init(std::size_t blockSize, unsigned char blocks);
        void Deinit();
        void* Allocate(std::size_t blockSize);
        void Deallocate(void* p, std::size_t blockSize);
        unsigned char* pData_;
        unsigned char firstAvailableBlock_;
        unsigned char blocksAvailable_;
    };
    

    Allocate() 和 Deallocate() 是用來分配使用 pData_ 指到的 memory 空間 (memory chunk),參數 blockSize 是要 allocate 的大小,對同一個 Chunk 永遠是固定的,為了節省空間所以存在上層。Memory chunk 分成多個相同 blockSize 的 block,每個 block 的第一個 byte 紀錄下個 available block 的 index,形成一個 linked list 來紀錄可用的 block。這是 memory management 常見的作法。因為只用一個 byte,所以一個 Chunk 最多有 256 個 block。

    在 Chunk 裡面,Allocate() 和 Deallocate() 都只要處理 linked list 的頭,速度很快。額外用的空間也佔很少比例。但是要注意,因為用 linked list 紀錄 available block,所以 Deallocate() 沒辦法檢查 double free,double free 會造成 linked list 壞掉。如果要解決這個問題,就要每個 block 多用一個 bit 紀錄是否 available。

    The Fixed-Size Allocator

    針對同一個大小的 allocation,使用同一個 FixedAllocator 來分配,就是這一層要處理的。因為他要控制多個 Chunk,可能需要 linear search 會降低效率,所以加了幾個技巧。

    1. 使用最簡單的 cache 機制,紀錄最後一個 allocated chunk, deallocated chunk。先找最後一個。
    2. 如果最後一個 allocated chunk 沒有空間,那就從頭開始 linear search。
    3. 如果最後一個 deallocated chunk 不是,那就從這個指標同時往上下找。這是為了改進兩種 memory 使用模式,一種是 delete 順序跟 new 相同,另一種 delete 順序跟 new 相反。
    4. 等到 Deallocate 有兩個空的 Chunk,才真的刪掉一個。這是為了避免 new & delete 反覆跨過邊界條件浪費時間。


    The SmallObjAllocator Class

    這個 class 處理任意大小的 memory 分配。如果太大,直接用 ::operator new/delete。如果夠小,就呼叫適當的 FixedAllocator 來處理。所以他有一個 vector<FixedAllocator> pool_,並且按照allocation 大小排序。

    跟 FixedAllocator 類似,SmallObjAllocator 也 cache 紀錄了最後一個用來 allocate & deallocate 的 FixedAllocator,為了讓連續相同大小的物件使用更有效率。


    SmallObject

    實作 static operator new & delete 來給其他 class 繼承。他的定義很簡單,但是他的 delete 有第二個參數 size,是比較特別的。一般 operator delete 只有一個參數,但是 C++ 也支援兩個參數的版本,對實作 SmallObjAllocator 比較有效率,可以省下很多搜尋的時間。而 compiler 實作的時候,可以透過 virtual destructor 或者 v-table 得到指標執行時候真正的 size,再把 size 傳給 operator delete。

    class SmallObject
    {
    public:
        static void* operator new(std::size_t size);
        static void operator delete(void* p, std::size_t size);
        virtual ~SmallObject() {}
    };
    

    其實這個部份我有疑惑,因為繼承 SmallObject 都會一併繼承 virtual desctructor,很多 compiler 會需要多存一個 vptr,這多使用的空間,和整個 allocation 的設計目的違背。而且,如果這邊沒有 virtual destructor,還是能夠繼承使用相同的功能,因此這邊不必 virtual,如果繼承以後有需要再加就可以了。為了避免直接 delete (SmallObject*)pFoo 沒呼叫到實際的 destructor,要把 ~SmallObject() 改成 protected。實際用 gcc 測試,struct Foo{ int a; }; 本身只要 4 bytes,多一個 virtual 會變成 16 bytes。改成 non-virtual protected destructor 就沒有問題。


    Singleton and Multithreading Issues

    設計基礎的 memory 分配的時候,整個系統都呼叫同一個 operator new,所以只能有一個 SmallObjAllocator 實體,同時也只能有一個 thread 使用。這邊很優雅的用 policy template 處理 multi-thread mutex lock 的實作,加上適當的 default 就完全不影響原本的設計,是很精彩的設計範例。

    Wednesday, March 19, 2014

    Typelists, Ch3, Modern C++ Design

    這章開頭先介紹使用 Typelist 的需要,其中一個情況是把 design pattern 套用到多個 type 的時候。這段動機不是很明確,先把這章當作練習 template 的暖身,也是很有趣味。就像 design pattern 一樣,需要的時候,就會感覺到這些小工具的好處。

    Defining Typelist

    Typelist 是把 type 串成一個像 linked list 結構的 template,定義很簡單。

    struct NullType {};
    
    template<class T, class U>
    struct Typelist
    {
        typedef T Head;
        typedef U Tail;
    };
    
    #define TYPELIST_1(T1)  Typelist<T1, NullType>
    #define TYPELIST_2(T1, T2)  Typelist<T1, TYPELIST_1(T2)>
    #define TYPELIST_3(T1, T2, T3)  Typelist<T1, TYPELIST_2(T2, T3)>
    ...
    

    使用的時候,只要把 data type 填入 TYPELIST_XX(...) 就可以得到一個 Typelist,像 linked list 一樣一個接一個。這個定義很像 std::pair,差別是 Typelist 沒有資料。

    Calculating Length

    接下來都是 template 寫法的練習。任一個 Typelist T 都是個 class type,如何 compile time 計算 T 裡面放幾個 type?用 class template 定義 enum 回傳數字。

    template<class T>
    struct Length
    {
        enum{ value = Length<typename T::Tail>::value + 1 };
    };
    template<class T>
    struct Length<TYPELIST_1(T)>
    {
        enum{ value = 1 };
    };
    

    這是很常見的 recursive template 寫法,先定義遞迴的方式,然後給 base case。這章裡面也有提出一個論證,說明 template 只能用 recursion,不能用 iteration,因為數值不能 reassign.

    Indexed Access

    另外一個問題是,要如何取出 Typelist T 裡面的第 N 個 type?跟計算長度很像,也是用 recursive 寫法計算,找到以後用 typedef 傳出來。

    template<class T, int N> struct IndexOf;
    template<class T, class U>
    struct IndexOf<Typelist<T, U>, 0>
    {
        typedef T Result;
    };
    template<class T, class U, int N>
    struct IndexOf<Typelist<T, U>, N>
    {
        typedef typename IndexOf<U, N-1>::Result Result;
    };
    

    這邊跟 Length 寫法不太一樣,差別在於 Length 有一般的形式,是靠 Typelist 的特徵 member type Tail 批配的,如果其他的 type 也有一樣的特徵,可能會被誤用。而 IndexOf 是提供的兩個 specialization,第一個參數 T 都指定是 Typelist,就算有一樣的特徵也不能用。書中的寫法是第二種,也就是較嚴格指定 Typelist 來做 specialization,當作 library 應該比較妥當。

    Class Generation with Typelists

    中間介紹了一些 Typelist 操作的工具,抓到 recursion 的訣竅以後都想得出來。這節要做一個新的突破,給定一個 Typelist 和一個 template class 如下,能不能有個 template class 自動幫我繼承 template class 套用 Typelist 產生的實體?

    TYPELIST_3(int, char, std::string)
    template <class T> struct Holder{ T value; }
    

    也就是說,要怎樣讓 GenScatterHierarchy<TYPELIST_3(int, char, std::string), Holder> 這個 class 自動多重繼承 Holder<int>, Holder<char>, Holder<string> 這三個?最簡單是直接寫個 template class 多重繼承 Holder<T1>, Holder<T2>, Holder<T3>,然後去讓 compiler 把真正的 type 填進去。但是 C++11 以前沒有變動長度的 template參數,所以每個長度都要寫一次。別忘了, template 一開始就是為了避免做一樣的事設計的。這邊也可以用。

    template<class T, template<class> class BASE> struct GenScatterHierarchy;
    template<class T, template<class> class BASE >
    struct GenScatterHierarchy<TYPELIST_1(T), BASE> : public BASE<T>{};
    template<class T, class U,   template<class> class BASE >
    struct GenScatterHierarchy<Typelist<T, U>, BASE> :
        public BASE<T>,
        public GenScatterHierarchy<U, BASE> {};
    

    這是最直接的想法,把 Holder 這個 template class 抽換成 BASE,並且每次抽出一個 type 繼承 BASE<T>,剩下的就用 recursion 繼承。注意 BASE 的宣告方式,因為沒給定參數不是一般的 class,要告訴 compiler 這是個 template class,而且只有一個 class 參數。

    96918602

    產生出來的繼承樹裡面,GenScatterHierarchy 本身是沒有任何 member 的,這就是巧妙的地方,因為 runtime 不佔空間,使用上也不會跟 BASE 的任何 member 發生命名衝突。因為是多重繼承,轉形成適當的 base type 就可以取用 BASE 的任何 member.

    // FieldTraits
    template<class T>
    struct FieldTraits;
    template<class TL, template<class> class BASE>
    struct FieldTraits< GenScatterHierarchy<TL, BASE> >
    {
        typedef TL Typelist;
        template<class U>
        struct Bind2
        {
            typedef BASE<U> Result;
        };
    };
    
    // field()
    // GenScatterHierarchy<TYPELIST_3(int, char, std::string), Holder> info;
    // int i = field<int>(obj).value;
    template<class T, class U>
    typename FieldTraits<U>::template Bind2<T>::Result& field(U& rhs)
    {
        return rhs;
    }
    

    上面的寫法也很巧妙,先例用 template function field() 自動代換 FieldTraits<U> 得到 U = GenScatterHierarchy<TYPELIST_3, Holder>,這樣就可以在 FieldTraits 裡面處理 Typelist,也可以使用 Holder 並且產生 Holder<T> 的實體 type。這是 traits 的技巧,直接用 function 很難達到。這邊的另外一個問題是,如果 Typelist 裡面有重複的 type,這樣的 cast 會變成 ambiguous error。事實上,這個情況產生繼承樹的時候,gcc 就會 warning 同一個問題。但是,如果多重繼承的時候,在中間加一層繼承,那麼 compiler 就能區分這樣要從哪條路 cast 回去,才能區分這個名字用的是哪個實體。(下面圖裡, D 所有的 member G 都有兩份)

    [A]^-[B], [A]^-[C], [B]^-[C], [C]-[note: C cannot cast to A directly.], [D]^-[E], [D]^-[F], [E]^-[G], [F]^-[G], [G]-[note: G cannot cast to D directly. Cast through F like (D&)(F&) is OK. ].png

    改進以後,新增一個 helper: template class ScatterLeftBase。注意他的第一個參是為了讓 compiler 可以區分這是兩個不同的實體 type,才把 Typelist 後面的部份一起傳進去。實際用來繼承的,只有 Typelist 第一個 type T。書裡沒有提到這部份,實作的時候要注意。

    template<class T, template<class> class BASE> struct ScatterLeftBase;
    template<class T, class U, template<class> class BASE>
    struct ScatterLeftBase<Typelist<T, U>, BASE> : public BASE<T> {};
    
    template<class T, template<class> class BASE> struct GenScatterHierarchy;
    template<class T, template<class> class BASE >
    struct GenScatterHierarchy<TYPELIST_1(T), BASE> : public ScatterLeftBase<TYPELIST_1(T), BASE>{};
    template<class T, class U,   template<class> class BASE >
    struct GenScatterHierarchy<Typelist<T, U>, BASE> :
        public ScatterLeftBase<Typelist<T, U>, BASE>,
        public GenScatterHierarchy<U, BASE> {};
    

    實作 cast 的時候,給定第 N 個 type,需要用 resursive function 一層層轉型回去,書中用 template function overload 來處理,但是也可以把 function 單獨放在 class 裡面,並且用 template class specialization 來區分使用哪個 function。用 function overload 需要一個額外的參數區別,放在 template class 裡面就是把這個額外的參數變成 template class 的參數,意思差不多,就看怎樣寫比較合理。

    template<int N, class T>
    struct Cast
    {
        template<class TL, template<class> class BASE>
        static T& cast(GenScatterHierarchy<TL, BASE>& rhs)
        {
            return Cast<N-1, T>::cast(static_cast< GenScatterHierarchy<typename TL::Tail, BASE>& >(rhs));
        }
    };
    template<class T>
    struct Cast<0, T>
    {
        template<class TL, template<class> class BASE>
        static T& cast(GenScatterHierarchy<TL, BASE>& rhs)
        {
            return static_cast< ScatterLeftBase<TL, BASE>& >(rhs);
        }
    };
    

    Monday, March 10, 2014

    Techniques, Ch 2, Modern C++ Design

    這章介紹基本的 template 技巧,雖然說基本,但是並不是很直接可以想出來的。

    Compile Time Assertions

    即使不用 template,static/compile time assert 也是 C++ 很常用的工具,但是 standard library 沒有提供。原理是用 template specialization,提供 true 的實作,但是不提供 false 的實作。

    template<bool> struct StaticAssertFail;
    template<>     struct StaticAssertFail<true>{};
    #define STATIC_ASSERT(value)  StaticAssertFail<value>()
    

    Partial Template Specialization

    Class template 可以只給定部份的參數特製化,但是 function template 不能,這直接看語法就懂了。也是很常用的功能。

    Local Classes

    C++ 允許在 function 定義裡面宣告 local class,就像 local variable 一樣。妙的是,local class 也可以繼承外面的 class,還可以把指標傳出去,就會變成外面無法看的 type。不過 local class 不能作為外面的 template 參數。

    Int2Type and Type2Type

    用 template 製造新的 type,可以作為 function overloading 區分用,因為製造出來的 type 不像原本的 type 會自動轉換。

    template<int N>
    struct Int2Type
    {
        enum{ value = N };
    };
    
    template<class T>
    struct Type2Type
    {
        typedef T Type;
    };
    

    Type Selection

    用 partial template specialization 實作下面的 template class,使得 IS_T == true 的時候,得到type T,否則得到 type U。

    template
    struct Select
    {
        typedef T Type;
    };
    

    Detecting Convertibility and Inheritance at Compile Time

    這是這章裡面最巧妙的部份,使用許多技巧偵測兩個 type 是否可自動轉型,這分成幾個步驟。

    1. 定義 class template 界面,規定 T 可以轉型成為 U 為 true
    2. 宣告兩個overload function Test 接受 T 為參數,如果 T 可以自動轉型成為 U,compiler 會選擇第一個 Test()。如果不行,就要選擇第二個 Test()
    3. 第一個 Test 直接接受參數 U,可自動轉型的情況
    4. 第二個 Test 接受其他的狀況,而且不能包含第一個 Test 的狀況。這邊比較妙,使用 Test(...) 因為 priority 低,又可以接受所有 type.
    5. 兩個 Test() 回傳不同 type
    6. 使用 sizeof() 是 compile time 就可以決定的特性,判斷 sizeof( Test( T() ) ) 的結果。為了用這個特定,兩個 Test() 回傳的 type 必須有不同的 sizeof().
    7. 因為 sizeof() 裡面不會真正執行,所以兩個 Test() 只需要宣告,不需要真正實作。但 enum exists 是 const expression,所以所有 function 都要是 static.

    template <class T, class U>
    struct Conversion
    {
    private:
        typedef char                   Small;
        typedef struct{ char c[2]; }   Big;
        static T _makeT();
        static Small _test(U);
        static Big _test(...);
    
    public:
        enum
        {
            exists = (sizeof(_test(_makeT())) == sizeof(Small))
        };
    };
    

    Type Traits

    提供 template 在 compile time 偵測 type 的資訊,包括是否為 pointer,是否為 fundamental type,最適合用來傳參數的 type 等。這裡面大部分都是用 template 列舉,就比較可以想得出來。

    Monday, March 03, 2014

    Policy-Based Class Design, Ch 1, Modern C++ Design

    這章介紹的重點,是使用 template 參數作為 class 的行為或者結構,使得 library class 的界面可以清楚分割,並且增進執行效率。原本的範例是 smart pointer,但是其實這邊的觀念和使用 virtual function 很像。比如一組 file I/O 的 library,要設計成可以操作 local file, stdin, stdout,也可以操作網路上的檔案,會把 bool write(...) 設計成這樣:

    template<class F>
    class Backup : public F
    {
        bool write(void* pData, size_t n)
        {
            return F().write(pData, n);
        }
    };
    
    struct LocalFile
    {
        bool write(pData, n){ return fwrite(pData, 1, n, pFile);
    };
    
    struct RemoteFile
    {
        ...
    };
    

    使用的時候,可以選擇不同形式的 policy 宣告。

    typedef Backup<LocalFile> LocalBackup;
    LocalBackup backup;
    backup.write(...); 
    

    也可以在 library 外面自訂 policy,產生新的組合:
    Backup<SocialNetworkProfile> facebookBackup;
    

    實作的困難點在於分割出要執行的 policy,還有制定 policy 的行為。上面的例子裡,template<class F> class Backup : public F 的繼承關係,可以讓 Backup 繼承 F 提供的功能,增加使用的彈性。