我們應該按照C中的convention去使用union,這是我這篇文章要給出的觀點。雖然C++使得我們可以擴充一些新的東西進去,但是,我建議你不要那樣去做,看完這篇文章之後,我想你大概也是這麼想的。
C由於沒有類的概念,所有類型其實都可以看作是基本類型的組合,因此在union中包含struct也就是一件很自然的事情了,到了C++之後,既然普遍認為C++中的struct與class基本等價,那麼union中是否可以有類成員呢?先來看看如下的代碼:
struct TestUnion
{
TestUnion() {}
};
typedef union
{
TestUnion obj;
} UT;
int main (void)
{
return 0;
}
編譯該程式,我們將被告知:
error C2620: union '__unnamed' : member 'obj' has user-defined constructor or non-trivial default constructor
而如果去掉那個什麼也沒乾的建構函式,則一切OK。
為什麼編譯器不允許我們的union成員有建構函式呢?我無法找到關於這個問題的比較權威的解釋,對這個問題,我的解釋是:
如果C++標準允許我們的union有建構函式,那麼,在進行空間分配的時候要不要執行這個建構函式呢?如果答案是yes,那麼如果TestUnion 的建構函式中包含了一些記憶體配置操作,或者其它對整個application狀態的修改,那麼,如果我今後要用到obj的話,事情可能還比較合理,但是如果我根本就不使用obj這個成員呢?由於obj的引入造成的對系統狀態的修改顯然是不合理的;反之,如果答案是no,那麼一旦我們今後選中了obj來進行 操作,則所有資訊都沒有初始化(如果是普通的struct,沒什麼問題,但是,如果有虛函數呢?)。更進一步,假設現在我們的union不是只有一個 TestUnion obj,還有一個TestUnion2 obj2,二者均有建構函式,並且都在建構函式中執行了一些記憶體配置的工作(甚至幹了很多其它事情),那麼,如果先構造obj,後構造obj2,則執行的 結果幾乎可以肯定會造成記憶體的泄漏。
鑒於以上諸多麻煩(可能還有更多麻煩),在構造union時,編譯器只負責分配空間,而不負責去執行附加的初始化工作,為了簡化工作,只要我們提供了建構函式,就會收到上面的error。
同理,除了不能加建構函式,解構函式/拷貝建構函式/賦值運算子也是不可以加。
此外,如果我們的類中包含了任何virtual函數,編譯時間,我們將收到如下的錯誤資訊:
error C2621: union '__unnamed' : member 'obj' has copy constructor
所以,打消在union中包含有建構函式/解構函式/拷貝建構函式/賦值運算子/虛函數的類成員變數的念頭,老老實實用你的C風格struct吧!
不過,定義普通的成員函數是OK的,因為這不會使得class與C風格的struct有任何本質區別,你完全可以將這樣的class理解為一個C風格的struct + n個全域函數。
現在,再看看在類中包含內部union時會有什麼不同。看看下面的程式,並請注意閱讀程式提示:
class TestUnion
{
union DataUnion
{
DataUnion(const char*);
DataUnion(long);
const char* ch_;
long l_;
} data_;
public:
TestUnion(const char* ch);
TestUnion(long l);
};
TestUnion::TestUnion(const char* ch) : data_(ch) // if you want to use initialzing list to initiate a nested-union member, the union must not be anonymous and must have a constructor。
{}
TestUnion::TestUnion(long l) : data_(l)
{}
TestUnion::DataUnion::DataUnion(const char* ch) : ch_(ch)
{}
TestUnion::DataUnion::DataUnion(long l) : l_(l)
{}
int main (void)
{
return 0;
}
正如上面程式所示,C++中的union也可以包含建構函式,但是,這雖然被語言所支援,但實在是一種不佳的編程習慣,因此, 我不打算對上面的程式進行過多的說明。我更推薦如下的編程風格:
class TestUnion
{
union DataUnion
{
const char* ch_;
long l_;
} data_;
public:
TestUnion(const char* ch);
TestUnion(long l);
};
TestUnion::TestUnion(const char* ch)
{
data_.ch_ = ch;
}
TestUnion::TestUnion(long l)
{
data_.l_ = l;
}
int main (void)
{
return 0;
}
它完全是C風格的。
所以,接受這個結論吧:
請按照C中的convention去使用union,盡量不要嘗試使用任何C++附加特性。
union是個好東西,union是個struct,裡面所有成員共用一塊記憶體,大小由size最大的member決定,存取成員的時候會以成員的類型來解析這塊記憶體;在gamedev中,union可以在這些方面有所作為:
1. 換名:
struct Rename
{
public:
union
{
struct
{
float x,y,z,w;
};
struct
{
float vec[4];
};
};
};
這樣我們既可以根據具體的含義來訪問變數,也可以象數組一樣的loop;
2 .壓縮:
struct Compression
{
public:
bool operator==(const Compression& arg) const { return value == arg.value; }
union
{
struct
{
char a,b,c,d,e,f,g;
};
struct
{
long long value;
};
};
};
這樣對於集中處理的情況,比如==,就會大幅度提高效率,象在64位機上,只要一次,或者傳輸資料的情況,壓縮解壓縮都非常方便;
3. 危險:
匿名的union用法,不是standard,所以在compiler上要確認==>編譯器移植性不好;
不同的機器作業系統上資料的size都是不一樣,表示不一樣,那麼在用union的時候,尤其是在移植的時候,都是危險的情況;
但是如果系統,compiler都是一樣的話,在合適的地方使用union還是可以的。