相信很多人很多時候都會想要把 enum 列舉的數值轉換成 string,方便寫出檔案或顯示到 UI 上,這種轉換的方法在很多高階語言,例如 C#、Java 等都有方法直接支援,但是 C/C++ 就沒這麼方便了。以下介紹幾種在 C++ 實作 enum 轉 string 的方法。

方法 1. 使用 switch case

  這個方法應該是最容易想到的方法了,以下是程式的範例:

enum EValue { KZero, KOne, KTwo };

const char* ToString(EValue value)
{
	switch (value) {
	case KZero:
		return "Zero";
	case KOne:
		return "One";
	case KTwo:
		return "Two";
	}
	return "Not Defined";
}

  使用的時候把想要轉換的 enum 當作引數呼叫 ToString 函數就可以了。實作簡單、直覺,但有個小缺點,當 enum 列舉的數量一大的時候,這個 switch case 會讓人覺得很討厭,有沒有辦法改善呢?

 

方法 2. 使用陣列索引進行查表

  這個方法是利用 enum 會從 0 開始列舉的特性,直接將 enum 值當作陣列索引值來查表,如以下範例:

enum EValue { KZero, KOne, KTwo };

const char* ToString(EValue value)
{
	static char *table[] = { "Zero", "One", "Two" };
	return table[value];
}

  使用方法一樣是呼叫 ToString 就可以了,但實作變得簡潔多了,是吧?不過這個方法一樣有個缺點,當 enum 列舉數量變多的時候,看程式碼時要比對就有點痛苦了,比如第 17 個列舉值對應的字串是什麼?是不是要慢慢數了呢?簡單的辦法是透過排版和註解來協助程式碼的閱讀,或者是使用 C99 語法,不過以下再介紹另一個辦法來改善這個問題。

 

方法 3. 使用 X Macro 來實作陣列查表

   X macro 是一種 C/C++ 的技術,簡單的說明和範例可以在維基百科查到,這裡便是要運用這個技術來改善方法 2 的小問題,參考以下程式碼:

#define VALUE_TABLE \
	X(KZero, "Zero") \
	X(KOne, "One") \
	X(KTwo, "Two")

#define X(a, b) a,
enum EValue { VALUE_TABLE };
#undef X

const char* ToString(EValue value)
{
#define X(a, b) b,
	static char *table[] = { VALUE_TABLE };
#undef X
	return table[value];
}

  這個方法的關鍵在於 X macro 的定義,雖然看起來有點可怕,仔細推敲一下,會發現這裡定義的 EValue 和 table[] 與方法 2 所定義的完全一模一樣,只是中間的程式碼透過前處理器以 VALUE_TABLE 取代了,但確實改善了 “索引” 和 “值” 之間比對的問題,在 VALUE_TABLE 中可以很明確地看出每一個 enum 列舉值所對應的 string。

  到這裡我們已經改善很多問題,但還有一個明顯的瑕疵,就是 enum 不一定要從 0 開始,也不一定要是連續整數,這時候該怎麼辦呢?

 

方法 4. 使用 X Macro 及 std::map 實作查表方法

  如果要實作非連續整數的查表,最簡單的方法就是運用 std::map 了,他是一個紅黑樹的實作,使用索引查詢一個值的時間複雜度是 O(logN)。參考以下範例:

#define VALUE_TABLE \
	X(KZero, = 0, "Zero") \
	X(KOne,  = 1, "One") \
	X(KTwo,  = 8, "Two")

#define X(a, b, c) a b,
enum EValue { VALUE_TABLE };
#undef X

const char* ToString(EValue value)
{
	static std::map<EValue, const char*> table;
	static bool isInit = false;
	if (isInit)
		return table[value];

#define X(a, b, c) table[a] = c;
	VALUE_TABLE
#undef X

	isInit = true;
	return table[value];
}

  我故意把 KTwo 的列舉值改成 8 ,以驗證這個函數是可以允許非連續的列舉值。這邊有運用了一個 isInit 的變數來避免表被重複初始化,X Macro 的部分,在 enum 是被展開成:

  KZero = 0,
  KOne = 1,
  KTwo = 8,

  在 table 的部分則是被展開成:

  table[KZero] = "Zero";
  table[KOne] = "One";
  table[KTwo] = "Two";

  相信大家仔細想一下應該看得懂。這裡是我學到這個方法參考網站,其中裡面有 C99 的實作方法,另外上面的 std::map 也是可以用 switch case 來做。