「不完全型別」指在C 中有宣告但又沒有定義的型別

2021-06-28 08:48:24 字數 2636 閱讀 9457

用delete刪除乙個只有宣告但無定義的型別的指標,是危險的。這通常導致無法呼叫析構函式(包括物件本身的析構函式、成員/基類的析構函式),從而洩露資源。

示例**:

引用

class c;                // 在另乙個cpp檔案中定義

c* createc();           // 在另乙個cpp檔案中定義

int main() 

初步分析:型別c沒有被定義,所以無法找到析構函式的原型(雖然析構函式是沒有返回型別、沒有引數,但這些資訊還不足以確定整個析構函式的原型。例如:析構函式是否是private?析構函式是否是virtual?這些資訊現在並不明確)。另外,也無法知道這個類是否過載了operator delete,但是最後一句delete p卻實實在在的通過編譯了。

這樣的結果很讓人困惑,為什麼在有如此多資訊不明確的情況下,delete p仍然可以通過編譯?其實也有乙個稍微好理解一點的情況:

引用

class c;

c& createc();

int main() 

這裡使用了c::operator&。按照c++語法,如果沒有定義c::operator&,則編譯器會為它自動產生乙個;如果定義了c::operator&,則編譯器不再會自動產生。由於此處型別c沒有被定義,顯然不可能知道是否定義了c::operator&,但是這段**仍然可以通過編譯。它的實際行為就好象沒有定義c::operator&一樣。也就是說,即使使用者(在別的地方)定義了c::operator&,編譯器也不會看到,但是這裡必須使用了,因此它自動產生乙個operator&。

回過頭來看前面的delete p,也可以理解了。編譯器沒有看到c::~c(),也沒有看到c::operator delete,那麼就當作程式設計師沒有定義這些內容。對於析構函式,本來編譯器應該在析構函式呼叫之前先呼叫基類的析構函式和成員的析構函式,但是現在基類和成員都無法確定,因此只有不呼叫。對於operator delete,編譯器沒有看到它,因此也當它不存在。

所以最後的結果就是:只釋放指標所指的記憶體,不呼叫析構函式,也不呼叫基類和成員的析構函式。換句話說,前面例子中的delete p,實際上已經變成了delete (void*)p。後面一種寫法的危險性是顯而易見的。

或許你認為這個情況很傻,幾乎不會遇到。在設計上,通常會成對的提供介面,比如有了createc就應該有destroyc。不過如果設計疏忽,上面的**又不會有任何編譯錯誤(實驗發現vc6會出現警告,但對於operator&則不會有任何警告),則最終會導致問題。

但是有的設計者可能會這樣做:提供乙個「std::auto_ptrcreatec()」,建立乙個物件,並且在不需要的時候銷毀之。由於std::auto_ptr內部仍然是呼叫的delete,所以問題仍然存在,並且埋藏得更深。

用boost::shared_ptr的話,情況就會好一些。假設main函式在a檔案,createc函式在b檔案,則對於std::auto_ptr來說,析構時實際上是在a檔案中呼叫delete,由於a檔案中class c沒有定義,所以出現問題。但對於boost::shared_ptr來說,在構造時就把delete作為「刪除器」傳入boost::shared_ptr內部,因此在析構時實際上是呼叫b檔案中的delete,由於在b檔案中需要實際的建立class c,所以b檔案通常是必須包含class c的定義的,這裡使用delete沒有問題。

把class c的析構函式定義為private的話,很容易的看到,std::auto_ptr的版本仍然可以通過編譯,但boost::shared_ptr的版本則出現錯誤了。

要知道「目前正在編譯的檔案中,是否存在某個型別的定義」,可以用sizeof。某些編譯器對未定義的型別執行sizeof會得到零(?),vc系列對於未定義型別執行sizeof會得到編譯錯誤。為了統一介面,可以用boost_static_assert(sizeof(c) >= 0);,或者在沒有安裝boost的時候直接寫static const char arr[sizeof(c)];

利用boost::checked_delete(當然還有乙個boost::checked_array_delete)代替delete,可以檢查型別是否已經有定義。如果未定義則給出乙個編譯期錯誤,如果有定義則直接執行delete操作。

最奇怪的就是:在類沒有定義的情況下,不是所有的operator都按照「編譯器預設產生的動作」來執行,例如使用operator=就會出現編譯錯誤。

引用

c* p = create();

*p = *p;         // 編譯錯誤。不會自動生成operator =

小結:對於乙個有宣告但未給出定義的「不完整型別」,使用它時需要特別小心。它的析構函式、operator delete、operator &等,即使在別的檔案中給出定義,在本檔案中由於沒有定義,仍然會按照編譯器預設的方式執行(最危險的地方就是忽略了自定義的析構函式)。

特別小心一些智慧型指標(例如std::auto_ptr)內部實際上也是使用delete,所以在使用時應該確保類定義可見。一定要避免這樣的設計:class c; std::auto_ptrcreate();

對於operator&,最好的做法就是不要過載它。

不完全型別

c 允許在乙個 檔案中存放多個類,但這樣往往不便於類的管理,所以一向是提倡乙個檔案中只存放乙個類。不過呢,隨著類規模的不斷膨脹,乙個檔案中存放乙個類也有些顯得臃腫,或者是在某個角度上不便於 的組織。因此,c 2.0中引入了不完全型別的概念,即啟用了新的修飾符partial。借助該修飾符,我們可以在多...

不完全型別

不完全型別指 函式之外 型別的大小不能被確定的型別 總結一下,c的型別分為 結構體的宣告就是乙個不完全型別的典型例子。struct woman tag struct man tag struct woman tag 這樣是沒問題的。如果將man tag結構中的struct woman tag wif...

不完全型別

有時候我們在一些編譯器寫 的時候會碰見不完全型別這個編譯錯誤,那麼什麼是不完全型別,為啥會出現呢 不完全型別指 函式之外 型別的大小不能被確定的型別 只能以有限方式使用。不能定義該型別的物件。不完全型別只能用於定義指向該型別的指標及引用 1 或者用於宣告使用該型別作為形參型別或者返回值型別。c的型別...