2013年1月10日 星期四

C++ enum member function

之前重構公司的程式碼,發現一個需求:希望 enum 能有 member function

現有的程式碼:

class PrimitiveKind {
    enum kind {
        VOID,
        BOOL,
        INT8, INT16, INT32, INT64,
        FLOAT32, FLOAT64,
        FUNCTION, OBJECT, ...
        ...
    };

    static bool isBool();
    static bool isInterger();
    static bool isFloat();
};

整理了一下使用到的情況,大概可以分成三種:

1. 判斷 isBool, isInterger, isFloat...

void f(PrimitiveKind::kind v) {
    if(PrimitiveKind::isBool(v)) {
        ...
    } else if (PrimitiveKind::isInteger(v)) {
        ...
    }
    ...
}

2. enum 的值互相比較

PrimitiveKind::kind promote(PrimitiveKind::kind a, PrimitiveKind::kind b) {
    if (a <= FLOAT64 && b <= FLOAT64) {
    if( a > b ) return a;
    else return b;
    } else {
        ...
    }
}

3. switch case

void g(PrimitiveKind::kind v) {
    switch(v) {
    case PrimitiveKind::VOID: ...
    case PrimitiveKind::INT8: ...
    case PrimitiveKind::INT16: ...
    }
}

這個 class PrimitiveKind 其實沒有任何 data member,她的角色比較像是 namespace。只是把跟 PrimitiveKind 有關的宣告集合起來。

我希望能避開 PrimitiveKind::isBool(v) 這種呼叫 static member function 的寫法,直接寫 v.isBool()。

第一個想法,很直覺想到可以讓 PrimitiveKind 變成一個 wrapper class,持有一個 PrimitiveKind::kind,並且把 static member function isBool() 改成 non-static member function isBool()。這樣我就可以滿足第一個狀況,可以直接呼叫 isBool(), isInteger(), isFloat()。但是卻沒辦法滿足後兩種狀況。因為 wrapper class 沒有 <, >, =, != 等等的 operator,也無法放進 switch。

第二個想法,我幫 PrimitiveKind 加上 operator overloading,讓他支援一整組的 relational operator,就可以也滿足第二個情況。

但這個時候我想了想,我應該先列出所有我需要滿足的需求,再來回顧我的設計。整理了一下,我希望我的 wrapper class 滿足以下條件:

1. 有 non-static member function 可以使用
2. wrapper 之間可以互相 assign,也可以被 raw enum value 直接 assign
3. wrapper 之間可以互相比較,也可以和 raw enum value 之間互相比較
4. 可以使用在 switch 裡面

其實最好是可以取代所有需要用到 raw enum 的地方。

其實前三條都很容易滿足(雖然很煩),但最後一條才是關鍵,為了讓他可以被用在 switch 裡面,我想我需要 overload 一個 cast operator(對,C++ 可以自己定義 cast operator,很少用到,但其實有這種功能 http://www.learncpp.com/cpp-tutorial/910-overloading-typecasts/ )

operator PrimitiveKind::kind() const { return kind_; }

讓他可以被 implicit 轉成 PrimitiveKind::kind,後來回頭一想,其實我前面 overoad 的那十幾個 relactional operator 好像都可以省下來,測試了一下,發現確實可以運作。

以下是最後的完成品。

class PrimitiveKind {
    enum kind { VOID, BOOLEAN, ... };

    operator kind() const { return kind_ ; }

    bool isBool();
    bool isInteger();
    bool isFloat();

    kind kind_;
}

使用簡單,用起來就像是 enum,可以放進 switch,可以直接比較,還可以有 member function,也封裝了 INT8 等等的 symbol(在 C++11 可以用 strong typed enum 達成)。