2011年5月30日 星期一

Boost 之 optional, assign 及 tribool

Boost 提供了一些小工具,讓程式碼可以更俱可讀性。

1. optional
    在寫程式時,常常會需要計算一些數值, 而這些數值有可能在某些條件下是沒有意義的,例如我們要算一個數的平方根時,一定得先檢查此數是否為非負數,若為負數,則此取平方根是沒有意義的(當然若是運算有考慮虛數則例外)。這時我們的程式片段大概長這樣:
    // double value = SOME_VALUE;
    ...
    if (value>=0) 
    {
       double sqrt_value = sqrt (value) ;
    }
    else
    {
        // error handling
    }
    
    這樣看起來其實沒什麼問題,但就設計哲學而言,client端 (sqrt函式的呼叫端) 必需先檢驗"條件",若通過後再真的"求值"。這樣的設計較為危險,因為 client必須去看文件,了解該函式的 domain 為何,再檢查 input 是否在該 domain 內,若是,再取值。把這樣的"知識"交由呼叫端來處理,可能帶來一些淺在的問題,例如呼叫端可能忘記檢查、可能檢查的條件是錯的,可能傳了負數進去得到了exception 卻沒處理,可能
    Boost 提供了一個小小的 wrapper class template 名為 boost::optional 來處理這種無意義值。使用上很簡單,client 以要封裝的 type 為 optional 的 template 參數宣告一個物件,將條件及求值式傳入 constructor。然後我們可以把這個 optional 物件像是測試 NULL pointer 一樣檢查其有效性 (支援 implicit conversion to bool),再用 operator* 取值 (因為 override 了 operator*) 。
    最後一步,就是在 library 端 (如果可以的話),或是 client 端寫個小小的 wrapper function 將條件封裝起來即可。
    #include<boost/optional.hpp>
    
    boost::optional<double> my_sqrt(double)
    {
       return boost::optional<double>(value>=0, sqrt(value)); 
    }
    
    void Foo( double value)
    {
       boost::optional<double> sqrt_value = my_sqrt( value );
       if(sqrt_value)
       {
          cout << *sqrt_value << endl;
       }
       else
       {
          cout << "value should not be negative." << endl;
       }
    }

    接著我們用 BOOST_AUTO 簡化一下程式碼,最後 client 就只要記得檢查 optional 是否有效,而不必知道如何檢查。
    #include<boost/optional.hpp>
    
    using namespace boost;
    
    optional<double> my_sqrt(double)
    {
       return optional<double>(value>=0, sqrt(value)); 
    }
    
    void Foo( double value)
    {
       BOOST_AUTO( sqrt_value, my_sqrt( value ));
       if(sqrt_value)
       {
          cout << *sqrt_value << endl;
       }
       else
       {
          cout << "value should not be negative." << endl;
       }
    }

    2. assign
    有時我們需要對 STL container 大量的塞一些常數性質的資料,像是 error code 或是 event messages 之類的,以往就是在程式裡寫一堆 push_back 或 insert。
    vector<int> v;
    v.push_back(1);
    v.push_back(3);
    v.push_back(4);
    v.push_back(7);
    v.push_back(9*9);
    
    map<int, string> m;
    m.insert ( make_pair ( 1, "one") );
    m.insert ( make_pair ( 2, "two") );
    m.insert ( make_pair ( 3, "three") );
    

    Boost 的 assign 函式庫實作了 "operator+="、逗號 "," 及 括號 "operator( )" 來簡化這些操作,下例結果同上。
    #include<boost/assign.hpp>
    using namespace boost::assign;
    
    vector<int> v;
    v += 1, 3, 4, 7, 9*9;
    
    map<int, string> m;
    m += make_pair ( 1, "one"), 
           make_pair ( 2, "two"), 
           make_pair ( 3, "three");
    
    

    使用 operator+= 在處理 map 時需要寫一大堆 make_pair,實在是很難看,我們可以用另一個 operator( ) 配合三個輔助函式insert( )、push_front( ) 及 push_back( ),更優雅地處理:
    #include<boost/assign.hpp>
    using namespace boost::assign;
    
    vector<int> v;
    push_back(v)(1)(3)(4)(7)(9*9);
    
    map<int, string> m;
    insert(m)( 1, "one")( 2, "two")( 3, "three");
    
    

    3. tribool
    一般我們在寫 C++ 要用到 Boolean 值時,通常有兩種型別可以用:BOOL 及 bool。由於早期的 C++ 並沒有 bool 這個關鍵字,所以各家 compiler 自行 typedef 或是 define 了 BOOL 這個型別 (或是 marco),通常它真正的型別是 int。例如微軟的 Windows API,而現代的 C++ (C99之後) 雖然加入了 bool 這個內建型別,但為了向下相容,Windows API 還是以 BOOL 來表示 Boolean 值。無論如何,現在應該儘量少用 BOOL 這個過時又不安全的東西了( 因為 BOOL 的值可能不為 TRUE 也不為 FALSE)。
    言歸正傳,bool 的 domain 應該只有 true 跟 false,這是廢話,但也因為這樣,我們無法用 bool 表示一個不確定的狀態。例如我們設一個 function 叫作 IsPrinterExists( ),用來偵測目前有沒有印表機存在,原型如下:
    bool IsPrinterExists();
    
    我們預期 IsPrinterExists( ) 應該回傳 true 表示有印表機,false 表示沒有,但如果偵測過程中有錯誤,那不管它回傳 true 或 false 都是不正確的(雖然設計上我們通常會當作 false 來處理,實務上大多也正確),應該要是一個 unknown 的值。
    Boost 引入一個新的型別,名為 tribool,它與 bool 差不多,但除了 true 跟 false 外還多了一個狀態 indeterminate。tribool 的 compare operation 有下列規則:
    • indeterminate 不是 true 也不是 false。
    • 任何與 indeterminate 比較的結果都是 indeterminate。
    • 對 || operator,只有與 true 的運算結果為 true,其它則為 indeterminate。
    • 對 && operator,只有與 false 的運算結果為 false,其它則為 indeterminate。
    • indeterminate 的 ! 運算結果還是 indeterminate。 
    • 可以使用 indeterminate( ) 來判斷是否為 indeterminate。 
    #include<boost/logic/tribool.hpp>
    
    using namespace boost;
    
    tribool tb(true);  // true
    tribool tb2(!tb)   // false
    
    if(tb)
    {
       // go here
       
       tb2 = indeterminate; // tb2 is indeterminate
       
       // OK, compare with indeterminate
       if( tb2 == indeterminate)
       {
          // go here
           cout << tb2 || true; // true
           cout << tb2 && false; // false
       }
       
       if(tb2)
           cout << "never printed";  // tb2 is not ture
       if(!tb2)
           cout << "never printed"; // tb2 is not false
       
       if( indeterminate(tb2) )
           cout << "indeterminate"; // yes
    }
    
    

    沒有留言: