12.06.07
再談 C++/CLI: Template Metaprogramming
Template metaprogramming 是 Metaprogramming 的技巧之一, 也是 Modern C++ 中重要的元素
基本上 Template metaprogramming 是利用 compiler 來產生程式碼(不是目的碼喔)的一個方法
至於要產生程式碼, 就有一個問題要考慮, 就是他的能力有多強
然而 Template metaprogramming 已經被證明是 Turing-complete
也就是他能表達所有程式語言能表達的程式(嚴格的說是演算法)
舉一個例子來說 基本上大家舉例都舉 factorial
我在這邊示範一下費伯納西數 費伯納西數得一個簡單的解法是使用遞迴
然而費伯納西數可不是一個 Divide & Conquer 的函式 (反而是 Divide & Complex)
所以不適合用遞迴 我們可以利用 Template metaprogramming 把計算費伯納西數的工作交給編譯器
這樣在執行時間 就不會花任何時間和記憶體(遞迴需要占用Stack Frame)來計算了
基本上 Template metaprogramming 用到了template 中有 template 的技巧
如此一來編譯器會把他遞迴展開 然後再利用特化來製造終止條件
看以下的程式碼
#include <iostream> using namespace std; template <unsigned int n> struct Fibonacci { enum { value = Fibonacci <n-1>::value + Fibonacci <n-2>::value }; }; template<> struct Fibonacci <0> { enum { value = 0 }; }; template<> struct Fibonacci <1> { enum { value = 1 }; }; int main() { cout << Fibonacci<5>::value << endl; }
看到這個程式 如果沒有覺得渾身不對勁 覺得這是邪魔歪道的 而且還覺得超炫
恭喜你 你可以繼續看下去了 Template metaprogramming 將會成為你讓人驚艷的技巧
這邊有一個地方要說明的 就是 enum hack
這是一個製造class 中 static const int 變數的方法
當然上述寫 static const int 也是可以行得通的
只是 大家比較習慣使用 enum hack
Fibonacci<15>::value 會在編譯時期就 計算好數值 610
C#, VB 在2.0 之後 也支持泛型了 那能不能使用 Template metaprogramming ?
基本上是不行的 .NET 的泛型 既不是在Compiler Time展開
也不能接受 non-type template parameter 更不用說有特化的能力
也基本上只有 C++/CLI 能使用 template 來達成這個技巧
然而 managed class 內不能接受 enum hack 的語法 所以要使用 static const int 來完成
下面來看Peter Simons用 Template metaprogramming 用來解質數的例子 請參考 prime-sieve.cc
/* * prime-sieve.cc -- written by Peter Simons <simons@cryp.to> * * This is an example of C++ template meta-programming. The interesting * part is the primeSieve<> meta template, which will generate a list * of integers in the range [2..n] using the Sieve of Erathostenes. */ ////////////////////////////////////////////////// // Meta-programming Infrastructure ////////////////////////////////////////////////// struct NullType { }; template<typename Head, typename Tail> struct Typelist { }; template<unsigned int Val> struct IntType { enum { value = Val }; }; template<bool flag, typename T, typename U> struct Select { typedef T Result; }; template<typename T, typename U> struct Select<false, T, U> { typedef U Result; }; ////////////////////////////////////////////////// // Alorithm to create [i..n] ////////////////////////////////////////////////// template<unsigned int i, unsigned int n> struct makeIntList { typedef Typelist< IntType<i>, typename makeIntList<i+1, n>::Result > Result; }; template<unsigned int n> struct makeIntList<n, n> { typedef Typelist< IntType<n>, NullType > Result; }; ////////////////////////////////////////////////// // The Sieve of Erathostenes ////////////////////////////////////////////////// template<unsigned int i, typename TList> struct sieveOne; template<unsigned int i, typename x, typename xs> struct sieveOne< i, Typelist<x,xs> > { typedef typename Select< x::value % i != 0, Typelist< x, typename sieveOne<i, xs>::Result >, typename sieveOne<i, xs>::Result >::Result Result; }; template<unsigned int i> struct sieveOne<i, NullType> { typedef NullType Result; }; template<typename TList> struct sieveAll; template<typename x, typename xs> struct sieveAll< Typelist<x,xs> > { typedef Typelist< x, typename sieveAll<typename sieveOne<x::value, xs>::Result>::Result > Result; }; template<> struct sieveAll<NullType> { typedef NullType Result; }; template<unsigned int n> struct primeSieve { typedef typename sieveAll<typename makeIntList<2,n>::Result>::Result Result; }; // 省略把 Algorithm 包裝成 Interator 的部分 ... // TEST #include <iostream> #include <iterator> using namespace std; int main(int, char**) { enum { n = 20 }; cout << "Finding prime numbers in [2.." << n << "]:\n"; toIterator<primeSieve<n>::Result > generator; generator(ostream_iterator<int>(cout, " ")); cout << "\n"; return 0; } // Result Finding prime numbers in [2..20]: 2 3 5 7 11 13 17 19
簡潔有力 效率高
12.03.07
STL/CLR: 2008 C++/CLI的利器
C++/CLI 雖然算是ISO C++ 相容的一個語言
但是就EC++說的一樣 C++ 是一個語言聯邦
在STL中運作是需要共識的 也因為這樣 Managed Type 在 STL 沒有辦法 很正常的使用
VS2008中 C++/CLI 中加入了一個大利器 STL/CLR 這是一個 STL在CLR中的實作版本
他將所有的元件放在 命名空間 cliext 內 如 cliext :: vector
組件檔是 Microsoft.VisualC.STLCLR.dll
當然最重要的是 cliext 內的容器是可以容納 Managed Type 的
為了能讓原始碼可以誇越STL和cliext
我們可以使用下面的巨集
#define _STL_OR_CLIEXT std 或 #define _STL_OR_CLIEXT cliext
接下來使用 _STL_OR_CLIEXT::vector 就可以更改 _STL_OR_CLIEXT 的 來切換兩者 了
範例如下
#ifdef _M_CEE
#include <cliext\vector>
#define _STD_OR_CLIEXT cliext
#else
#include <vector>
#define _STD_OR_CLIEXT std
#endif
如同STL, 在cliext的容器 中運作是也是需要共識的
以下為想在cliext中運作的 Type (當然包含Managed Type ) 需要的共識
最重要的當然是和別的容器互相利用囉 (如果沒有 大家也不會用他吧)
需要互相利用的容器分成兩大類 一個是.NET Collection 一個是 STL Container
先談和 .NET Collection 的部分 : 這部分問題比較小 cliext 有可以接受.NET Collection 的建構函式 可以使用
或是利用cliext::collection_adapter 來進行轉換
逆向操作則是使用 cliext::make_collection
STL的部分 要使用VC2008的新功能 msclr::interop::marshal_as
支援的型態如下表
| From Type | To Type | Marshal method | Include file |
| System::String^ const char * char * System::String^ const wchar_t * wchar_t * System::IntPtr HANDLE System::String^ BSTR System::String^ bstr_t System::String^ std::string System::String^ std::wstring System::String^ CStringT<char> System::String^ CStringT<wchar_t> System::String^ CComBSTR |
const char * System::String^ System::String^ const wchar_t* System::String^ System::String^ HANDLE System::IntPtr BSTR System::String^ bstr_t System::String^ std::string System::String^ std::wstring System::String^ CStringT<char> System::String^ CStringT<wchar_t> System::String^ CComBSTR System::String^ |
marshal_context marshal_as marshal_as marshal_context marshal_as marshal_as marshal_as marshal_as marshal_context marshal_as marshal_as marshal_as marshal_as marshal_as marshal_as marshal_as marshal_as marshal_as marshal_as marshal_as marshal_as marshal_as |
marshal.h marshal.h marshal.h marshal.h marshal.h marshal.h marshal_windows.h marshal_windows.h marshal_windows.h marshal.h marshal_windows.h marshal_windows.h marshal_cppstd.h marshal_cppstd.h marshal_cppstd.h marshal_cppstd.h marshal_atl.h marshal_atl.h marshal_atl.h marshal_atl.h marshal_atl.h marshal_atl.h |
以下為所有的 cliext 元件列表
- adapter (STL/CLR)
- algorithm (STL/CLR)
- deque (STL/CLR)
- functional (STL/CLR)
- hash_map (STL/CLR)
- hash_multimap (STL/CLR)
- hash_multiset (STL/CLR)
- hash_set (STL/CLR)
- list (STL/CLR)
- map (STL/CLR)
- multimap (STL/CLR)
- multiset (STL/CLR)
- numeric (STL/CLR)
- priority_queue (STL/CLR)
- queue (STL/CLR)
- set (STL/CLR)
- stack (STL/CLR)
- utility (STL/CLR)
- vector (STL/CLR)
11.29.07
Implementing the Singleton Pattern in C#
基本上 Singleton Pattern 可以有很多實作方法
而 Singleton Pattern 中比較重要的考量點有兩個
- Thread Safe
- Lazy Binding
在C#中,最簡單的實作方式就是
public sealed class Singleton { static Singleton instance = null; Singleton() {} public static Singleton Instance { get { if (instance==null) { instance = new Singleton(); } return instance; } } }
Lazy Binding - OK!
Thread safe - NO
如果為了滿足 Thread Safe
public sealed class Singleton { static Singleton instance=null; static readonly object objlock = new object(); Singleton() { } public static Singleton Instance { get { lock (objlock) { if (instance==null) { instance = new Singleton(); } return instance; } } } }
Lazy Binding - OK!
Thread safe - OK!
可惜它有一個很大的缺點 lock 的時間消耗是 if的好幾個數量級
接下來是使用OS中最普遍解法 double checking
if (instance==null) { lock (objlock) { if (instance==null) { instance = new Singleton(); } } } return instance;
當然 其實程式語言也可以很美 利用C#語言的特性
public sealed class Singleton { static readonly Singleton instance = new Singleton(); static Singleton(){} Singleton(){} public static Singleton Instance { get { return instance; } } }
又簡短又正確 可惜它失去Lazy Binding 的能力
想加入Lazy Binding 需要作一些苦工
public sealed class Singleton { Singleton(){} public static Singleton Instance { get { return SingletonPorvider.instance; } } class SingletonPorvider { static SingletonPorvider(){} internal static readonly Singleton instance = new Singleton(); } }
最後 把他泛化
// NON Lazy Binding 版本
public class Singleton<T> where T : new() { private static T instance = new T(); static Singleton(){} Singleton(){} public static T Instance { get { return instance; } } }
// Lazy Binding 版本
public class Singleton<T> where T : new() { Singleton(){} public static T Instance { get { return SingletonPorvider.instance; } } class SingletonPorvider { static SingletonPorvider() { } internal static readonly T instance = new T(); } }