2011年5月6日 星期五

C++0x 及 Boost 的自動型別推導:auto、decltype 及 BOOST_AUTO、BOOST_TYPEOF

C++是個 static strong typing (靜態強型別) 的語言,因此所有變數在使用前都必須明確宣告其型別,這帶給了 C++ 執行快速的優點,並俱有較高的安全性。然而隨著 STL 及 自訂型別的大量使用,也帶來了一些寫程式上的小問題。

舉例來說,我們常常要為 STL container 宣告 iterator 時,往往會寫這樣子的程式碼:

std::map<int, std::string> myMap;
...
...
std::map<int, std::string>::iterator itor = myMap.begin();
// do something with itor

其中我們的變數名稱只有四個字,而型別名稱卻有三十多字,若我們有許多這樣子的 container 及宣告,那程式碼中必然充斥著滿滿的宣告,而降低可讀性。對於這種問題,傳統的做法就是使用 typedef 或是 marco,但都只能解決一小部分的問題,就是程式碼看起來比較乾淨,但對於程式員而言,還是需要記得這冗長的型別名稱。myMap.begin( ) 回傳值的型別,你知我知 Compiler 也知,甚至在編譯期就可以確定了,那我們能夠少打這些多餘的字嗎?


C++0x 提出了兩個關鍵字來解決這問題:auto 及 decltype。

  • auto 是個從 C 語言繼承而來的關鍵字,在 C++0x 之前,auto 是一種 storage-class specifiers,用來修飾變數宣告的 scope、lifetime 及 storage。如果一個變數的宣告前加上 auto 的話,那就表示是宣告一個 Local variable。如果一個變數宣告沒有加任何的 storage-class specifiers (如 static、register、extern …等),那預設就是auto。因為如此,auto 很少被使用。
    // Variable i is explicitly declared auto (local variable).
    auto int i = 0;
    // Variable j is implicitly declared auto (local variable).
    int j = 0; 
    在 VC2010 及 C++0x 中,auto 有了新的作用,用來表示一個經由 compiler 推導的型別 (Deduce Variable Type)。你可以在變數宣告前加上 auto 並給予初始值,該變數的型別會根據初始值來決定。可以被推導的型別有:const、volatile、pointer、reference 及 rvalue reference。
    我們可以在compile時加上參數。以選擇我們對於 auto 的使用方式:
    • /Zc:auto- : 表示將auto當成舊式的 storage-class specifiers
    • /Zc:auto (default): 表示將auto當成新式的 deduce variable type 

    // 宣告
    // Variable j is explicitly type int.
    int j = 0;
    // Variable k is implicitly type int because 0 is an integer.
    auto k = 0; 
    
    
    // 宣告 iterator (由其在loop裡)
    deque<double> dq1(2,0.1);
    
    for (auto it = dq1.begin(); it != dq1.end(); ++it)
    {  
        // do something
    }
    
    for_each(auto elem in dq1)
    {  
        // do something
    }
    
       
    // 宣告 pointer
    double x = 12.34;
    auto *y = new auto(x), **z = new auto(&x);
    
    
    // More
    auto x = 1, *y = &x, **z = &y; // Resolves to int.
    auto a(2.01), *b (&a);         // Resolves to double.
    auto c = 'a', *d(&c);          // Resolves to char.
    auto m = 1, &n = m;            // Resolves to int.
    
    
    // 搭配 :? 使用
    int v1 = 100, v2 = 200;
    auto x = v1 > v2 ? v1 : v2;
    
    
    // 搭配 const 使用
    int f(int x) { return x; }
    
    int main()
    {
        auto x = f(0);
        const auto & y = f(1);
        int (*p)(int x);
        p = f;
        auto fp = p;
        ...
    } 

    auto 雖然好用,但它並非萬能,以下為一些誤用 auto 的情況 (以下皆使用新式 auto,即 Compile with /Zc:auto )
    • 不能跟其它的 type-specifier 一起使用
     // error
    auto int x;
    • 用 auto 宣告變數必需給初始值
     // error
    auto x1;                 
    auto y1, y2, y3;         
    auto z1 = 1, z2, z3 = -1; 
    
    • auto 不可當作函式回傳型別, 亦不可當作 array 型別
     // error
    auto f( ){ }
    auto a[5];           
    auto b[1][2];        
    auto y[5] = x;       
    auto z[] = {1, 2, 3}; 
    auto w[] = x; 
    
    • auto 不可當作參數, 或 template 參數的型別
     // error
    void f(auto j){}
    
    template<class T> class C{};
    int main()
    {
       C<auto> c;   // C3539
       return 0;
    }
    
    • auto 搭配 new 使用時必需有初始值
     // error
    new auto();         
    auto x = new auto(); 
    
    • auto 不可被推導成 void, 亦不可宣告一個 auto pointer, 卻代入一物件(或內建型別)當初始值
     // error
    void f(){}
    auto x = f();
    
    auto* x = 123.0;
    
    class A { };
    A x;
    auto *p = x; 
    
    • 不可以該 auto 變數來自我初始化
    // error
    auto a = a;   
    auto b = &b;   
    auto c = c + 1; 
    auto* d = &d;  
    auto& e = e; 
    
    • 不可將其它型別轉型(cast)成 auto 型別
     // error
    auto(value);                      
    (auto)value;                     
    auto x1 = auto(value);            
    auto x2 = (auto)value;          
    auto x3 = static_cast<auto>(value); 
    
    • 在同一行所宣告的 auto 變數, 都必需被推導成同一型別
    // error
    // Variable x1 is a pointer to char, 
    // but y1 is a double.
    auto * x1 = "a", y1 = 3.14; 
    
    // Variable c is a char, 
    // but c1, c2, and c3 are pointers to pointers.
    auto c = 'a', *c1 = &c, * c2 = &c1, * c3 = &c2; 
    
    // Variable x2 is an int, 
    // but y2 is a double and z is a char.
    auto x2(1), y2(0.0), z = 'a'; 
    
    // Variable a is a pointer to int, 
    // but b is a pointer to double.
    auto *a = new auto(1), *b = new auto(2.0);
    
    • 不可對 auto 使用 sizeof 或 typeid
    auto x = 123;
    sizeof(x);    // OK
    sizeof(auto); // Error
    
    auto x = 123;
    typeid(x);    // OK
    typeid(auto); // Error
    

    • decltype 是 C++0x 新增的一個關鍵字,它接受一個 expression,並由該 expression 推導出一個型別。
    const int&& foo();
    int i;
    struct A { double x; };
    const A* a = new A();
    
    decltype(foo()) x1; // type is const int&&
    decltype(i) x2; // type is int
    decltype(a->x) x3; // type is double
    decltype((a->x)) x4; // type is const double&
    

    如今,auto 及 decltype 幾乎完全確定會制定於 C++0x當中了,但也是未來的 compiler 才會支援。這麼好用的東西,現在不能用實在是很可惜,所以 Boost 提供了兩個 marco 來模擬 auto 及 decltype。它們分別是BOOST_AUTO 及 BOOST_TYPEOF。
    • BOOST_AUTO 的用法為 BOOST_AUTO( Var, Expr),它會依 Expr 推導的型別宣告一個名為 Var 的變數,並初始化為 Expr 的值。
    • BOOST_TYPEOF 的用法為 BOOST_TYPEOF( Expr ),結果為一個經由推導 Expr 而成的型別指示詞 (Type specifier)。
    #include <boost/typeof/typeof.hpp>
    
    vector<string> func()
    {
        vector<string> v(10);
        return v;
    }
    
    int main()
    {
        BOOST_TYPEOF(2.0*3) x = 2.0*3;
        BOOST_AUTO(y, 2.3) ;
    
        BOOST_AUTO(&a, new double[20]);
        BOOST_AUTO(p, make_pair(1, "string"));
        BOOST_AUTO(v, func() );
        
        return 0;
    }
    

    參考資料:
    http://msdn.microsoft.com/en-us/library/dd465215.aspx
    http://www.boost.org/doc/libs/1_46_1/doc/html/typeof/tuto.html

    沒有留言: