C++中使用union的幾點思考

C++中使用union的幾點思考

 

大衛注:
這段時間整理舊資料,看到一些文章,雖然講的都是些小問題,不大可能用到,但也算是一個知識點,特整理出來與大家共享.與此相關的那篇文章的作者的有些理解是錯誤的,我寫此文,也是糾正爲了作者的一些錯誤認識.當然,如果我的理解有任何錯誤,也懇請大家批評指正.

C++
雖說被B.S.稱作一門新語言,但它畢竟與C有着千絲萬縷的聯繫,雖然B.S.一再堅持,但我還是願意把C++看作是C ++.
我們應該按照C中的convention去使用union,這是我這篇文章要給出的觀點.雖然C++使得我們可以擴展一些新的東西進去,但是,我建議你不要那樣去做,看完這篇文章之後,我想你大概也是這麼想的.

C
由於沒有類的概念,所有類型其實都可以看作是基本類型的組合,因此在union中包含struct也就是一件很自然的事情了,到了C++之後,既然普遍認爲C++中的structclass基本等價,那麼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,因爲這不會使得classC風格的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++附加特性.

posted on 2004-11-12 09:08 大衛的思維空間 閱讀(8069) 評論(16)  編輯 收藏

# "比較權威的解釋", B.S權威不?可惜你不看他的書。

以下摘自《 the c++ programming language 3rd 》,對爲什麼union不支持構造函數和析構函數,推薦怎麼使用unions都有說明。

10.4.12 Unions [class.union]
A named union is defined as a struct, where every member has the same address (see §C.8.2). A
union can have member functions but not static members.
In general, a compiler cannot know what member of a union is used; that is, the type of the
object stored in a union is unknown. Consequently, a union may not have members with constructors
or destructors. It wouldn’t be possible to protect that object against corruption or to guarantee
that the right destructor is called when the union goes out of scope.
Unions are best used in lowlevel
code, or as part of the implementation of classes that keep
track of what is stored in the union (see §10.6[20]).

其實正如在vckbase/c++論壇上我對你說的,想想union的用途就可以了。union的出現是爲了解決一組不定類型數據的空間緊張問題,它是爲省空間用的,不是爲了封裝數據和方法。所以我覺得大衛兄太過於刨根究底了,而忽略了C++的實用性(實用性是C/C++的首要設計思想)

2004-11-12 10:18 | 一笑

# re: (大衛的閱讀筆記)C++中使用union的幾點思考

謝謝笑兄給出的CPL中的相關論述.我現在主要關注的內容並非C++(雖然C++是我最喜歡的語言),我在blog上發佈這些文章也只是希望談一下自己對於某些語言特性或新技術的認識,以期對後來者有些許幫助,同時,也希望在與大家的交流中提高自己.
^_^,
也許我真的有點刨根究底,不過笑兄的關於"union是爲省空間用的"的觀點我不認同,雖然,我也看到過類似的說法,但是,union怎麼可能節省空間呢?:
union uT
{
int i;
char c;
};
當他被作爲char使用時,明明浪費了空間.
只是從另一角度出發:把它當成一種特殊的struct,struct相比時纔可以勉強說是節省了空間.
我認爲union的引入,更多地是提供了一種解決多種型別共同使用同一空間(這與節省空間是兩回事)的機制,藉助這種機制,我們可以提供更豐富的語言表現力.
爲此,我決定再寫一篇文章,談一些比較有意思/有意義的union的使用,請關注 & 批評指正.

2004-11-13 05:11 | Bill David

# union的進一步認識與一些深層應用[TrackBack]

Ping Back來自:blog.csdn.net
Bill David
引用了該文章,地址:http://blog.csdn.net/billdavid/archive/2004/11/26/195006.aspx

2004-11-26 18:58 | Bill David

# re: (大衛的閱讀筆記)C++中使用union的幾點思考

呵呵,仁者見仁,智者見智。
但是union的確應該是節省空間使用的。當時的初衷是這樣的。但是後來就不一定了。3ks 二位。

2005-04-19 17:38 | 漫天飛舞

# 今日經論壇匿名大俠指點,union還有一個重要的特性

union不支持繼承。

特記錄於此。

2005-04-19 17:59 | Bill David

# re: union不支持繼承。

我突然覺得如果union設計爲可繼承的也未嘗不可。
union base
{
    int mem1;
    char mem2;
};
這個假設被原實現者設計好了,突然,某天我想在base的基礎上添加一些特性,於是我寫
union derive : base
{
    short mem 3;
};
或許可能實際意義不大吧!但,如果這麼說,我reinterpret_cast成員mem1呢?union的實際意義又有多大呢?

不知道你怎麼看?

2006-05-16 13:35 | 清風雨

# re: union的幾點思考

又看了你的另一篇union的,可能我比較懶惰的緣故,我只得到一個結論: union僅僅只是爲了方便。

2006-05-16 13:49 | 清風雨

# re: (大衛的閱讀筆記)C++中使用union的幾點思考

這都是些什麼?

2006-05-18 21:35 | 一二三

# re: (大衛的閱讀筆記)C++中使用union的幾點思考

看不懂

2006-05-18 21:36 | 一二三

# re: (大衛的閱讀筆記)C++中使用union的幾點思考

union
{
    struct
           {
                string name;
           }info;
    int  age;
}information;

上面的語句可能出錯,has copy constructor.   難道只有把string -->char *??

2006-07-07 15:38 | Tmin

# re: (大衛的閱讀筆記)C++中使用union的幾點思考

看了文章和回帖,我非常同意一笑兄的看法。我覺得作者沒有完全理解節省空間的指的是如何節省,例如我們獲取一些信息時,需要把信息數據填充到一個結構中,但是我們需要的信息的數據的類型會根據情況不同而不同,如果我們爲了通用的話,可能就需要爲所有可能出現的類型都預留相應的空間來存儲,但是每次只能使用其中一個有效。其他預留無效。這顯然是浪費空間的。所以union使得不同類型數據存放在內存的同一個位置,當然這個空間的大小是能容納所有類型的數據的。因爲unicon的數據類型是運行時決定的,事先並不知道,所以爲union做一個構造函數是沒有意義的。

2006-10-17 11:48 | hoodlum

# re: (大衛的閱讀筆記)C++中使用union的幾點思考

剛纔回的倉促,可能有論述不是很恰當。但是看了作者的另一篇文章裏面引用的英文,論述的也和我本來的想法是一致的。也就是說,可以使用union.memberData來訪問成員,但是應該注意的是,使用時只有其中一個成員纔是有意義的。union佔用的空間大小是所有成員數據大小的最小公倍數。(因此,轉爲託管代碼中就可以使用一個能夠容納union大小的數據類型來代替,或者用通用的字節數組)

例如下面的例子(來自Windows API):
union
{    
   DWORD dwOemId;    
   struct {
                 WORD wProcessorArchitecture; 
                 WORD wReserved; 
              };
  };

它通常佔用4個字節,因此可以使用託管代碼中的Int32來代替。
其中dwOemId是爲了向前兼容某些系統版本,當前已經棄用。這樣,一個程序對API的調用可以在不同版本的系統中運轉良好,處理各自自己需要的數據。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章