從這章開始,這本書使用 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 包裝起來,不用每次都重複寫。包裝起來需要做到下面的功能:
- Create 的時候,要能設定 Receiver, action, 還有 action 的參數。
- Command::Execute() 可能有不同的 return type, parameter type,每一組 prototype 都是一個 class,所以要用 class template 來做。
- Create 的時候,可以給已知的參數,這樣 prototype 就會改變。
- 可以產生 MacroCommand,可以一次執行多的 ConcreteCommand.
- 可以被 Invoker 執行,並且呼叫正確的 action.
- C++ 的 callable entity 都要能使用。
- Function pointer
- Functor (class with operator())
- Pointer to member function
- 因為 Command + Execute() 本身就跟 functor 結構一樣,而且這邊設計的萬能 Command 需要同時能夠呼叫 functor 和其他 Command,就統一使用 operator() 代替 Execute,並且把 class 命名為 Functor。(要自動判斷呼叫 operator() 還是 Execute(),應該很難設計)
Functor
整理一下上面的需求,可以得到下面的功能列表。
- Functor 的 prototype (return type + parameter type) 是 template 分類的方式。
- 因為 parameter type 長度不固定,用 Typelist 來處理。
- Constructor 有三種,分別負責記住 function pointer, functor, pointer to member function.
- Overload operator() 來執行 constructor 給的參數。
- 內部處理不同 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。