公司的程式 (特別是那種底層 API) 其實很常使用 union 來減少非必要的記憶體使用量,只是如果使用 union 的話就必須要能夠知道該用哪種型別來存取 union 中的變數,因此很常出現這種程式碼:
enum Type {Int, Double};
struct Data {
Type type;
union {
int i;
double d;
};
};
int main()
{
Data data;
data.type = Int;
data.i = 3;
data.type = Double;
data.d = 3.14;
return ;
}
簡單來說就是設定好要存取的是哪種型態 (然後用個 enum 來表示),接著 API 會根據設定的型態來對 union 中的變數做存取。以上面的範例來說,會根據 Data 中的 type 是被設定成 Int 還是 Double 來決定 是要存取 data.i 還是 data.d。
因為個人覺得這樣滿容易手殘的,特別是公司的 API 其實型別種類有點多 |||Orz,所以想要簡化 API 的使用方式,希望可以直接根據所設定的型別存取對應的變數就好,畢竟這種關聯性其實是固定的,利用 generic programming 應該是不難做到的。
先來個 C++11 能使用的方法:
template <int N> struct UType; template <> struct UType<Int> { typedef decltype(Data::i) type; }; template <> struct UType<Double> { typedef decltype(Data::d) type; }; template <int N> typename UType<N>::type get_data(Data& data); template <> UType<Int>::type get_data<Int>(Data& data) { data.type = Int; return data.i; } template <> UType<Double>::type get_data<Double>(Data& data) { data.type = Double; return data.d; } int main() { Data data; data.i = 3; std::cout << "value: " <<
get_data
<Int>(data) << '\n' << "type: " << data.type << '\n'; data.d = 3.14; std::cout << "value: " <<
get_data
<Double>(data) << '\n' << "type: " << data.type << '\n'; return 0; }
概念其實很簡單:先定義一個 struct UType,裡面只有一個 typedef 後的型別 type,利用全特化 (full specialization) 就可以讓 type 跟據 enum 的值來改變應該是哪種型別。
接著再自訂另一個 function 當介面 (上面的例子中就是 get_data),回傳型別就只要利用剛剛定義好的 Utype<N>::type 即可,這樣就可以根據 enum 的值讓 compiler 決定應該是哪種型別;回傳值呢? 一樣利用全特化來決定應該回傳 union 中的哪個變數,如果給的是 Int 那就回傳 data.i;如果是 Double 就回傳 data.d。設定好這個中介介面後就能像 main 中那樣的使用方式只指定要使用的是哪種型別就可以讓 compiler 挑到適合的介面來得到正確的值。
上面的例子雖然說是用 C++11,不過其實只要把 decltype 改掉,直接寫明是用哪種型別的話,在 C++98 應該也是可以正確使用的。
好的,其實這問題在 C++14 會變得更簡單,因為 C++14 有 auto return type deduction,所以其實可以簡化成下面這樣:
template <int N> auto get_data(Data& data);
template <>
auto get_data<Int>(Data& data)
{
data.type = Int;
return data.i;
}
template <>
auto get_data<Double>(Data& data)
{
data.type = Double;
return data.d;
}
int main()
{
Data data;
data.i = 3;
std::cout << "value: " << get_value<Int>(data) << '\n'
<< "type: " << data.type << '\n';
data.d = 3.14;
std::cout << "value: " << get_value<Double>(data) << '\n'
<< "type: " << data.type << '\n';
return 0;
}
對,連 UType 都免了...C++11 開始雖有不少噁心的東西出現,不過方便的東西其實也不少,至少程式碼真的有機會簡潔許多。
沒有留言:
張貼留言