12.06.07

再談 C++/CLI: Template Metaprogramming

發表於 C++/CLI, Programming Language tagged , 於 2:04 am 由 zusecheng

Template metaprogrammingMetaprogramming 的技巧之一, 也是 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, Programming Language tagged , 於 8:22 am 由 zusecheng

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 ) 需要的共識

  • A public copy constructor.
  • A public assignment operator.
  • A public destructor.這東西最重要的功能除了正常的STL功能外
    最重要的當然是和別的容器互相利用囉 (如果沒有 大家也不會用他吧)
    需要互相利用的容器分成兩大類 一個是.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 元件列表

  • 11.29.07

    Implementing the Singleton Pattern in C#

    發表於 C#, Programming Language 於 5:42 am 由 zusecheng

    基本上 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();
      }
    }