Tuesday, April 07, 2015

Installing Apple Thunderbolt Display

Hardware

I use GA-Z97X-UD7 TH, which has 2 thunderbold  port (TH). Another video card is also adopted, but I didn't expect the video card output through TH.

Software

Windows 8.1.

The Results

I use another D-SUB display to install Win, and the TH display works after TH driver (from Gigabyte) installed. Apple Thunderbolt Display also has speaker, microphone and video camera integrated, and only Face Time HD Camera needs additional driver.
There is NO official driver to Face Time HD Camera. The driver is supposed to be downloaded from BootCamp client on MacOS, but there are a couple un-official versions, which are likely copies from official one . It works good after installing the driver.

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。