對比Go等其餘語言的工程,C++工程讓人痛苦的一件事情就是當工程稍微龐大一點,編譯時間就蹭蹭蹭往上爬。通常來講看過Effective C++這本書或者其餘相似書籍的人都知道要解決編譯時長的問題,就要解決好和頭文件之間的依賴關係。因此在任何須要的時候要首先考慮使用前置聲明而不是之間include頭文件。也就是說,在定義類的時候成員變量若是是自定義類型,能夠考慮將其聲明爲指針類型或者是配合智能指針。函數傳參時也是同樣,使用指針或者引用。app
對於一個C工程來講,由於沒有智能指針和引用的概念,因此都是直接使用指針配合前置聲明。用起來駕輕就熟。ide
可是C++工程裏,有時候爲了方便和省心,更多時候指針類型的成員變量會使用智能指針包一下。這個時候有可能會出現編譯通不過的狀況:函數
MSVC:this
error C2338: can't delete an incomplete type
warning C4150: deletion of pointer to incomplete type 'base';spaClang:指針
error : invalid application of 'sizeof' to an incomplete type 'base'
static_assert(0 < sizeof (_Ty),
^~~~~~~~~~~~
note: in instantiation of member function 'std::default_delete<base>::operator()' requested here
this->get_deleter()(get());
^
./main.h(6,8) : note: in instantiation of member function 'std::unique_ptr<base, std::default_delete<base> >::~unique_ptr' requested here
struct test
^
./main.h(5,8) : note: forward declaration of 'base'
struct base;code
看到這裏,仍是要感謝下clang的輸出,比較清楚地把問題的本質緣由找出來了。可是等等,我哪裏調用了智能指針的析構函數?對象
稍微有點警覺的狀況下,你應該反應過來是默認的析構函數在作析構智能指針這事情。blog
咱們先來作一個嘗試,把默認的析構函數顯示寫出來,而後按習慣把析構函數的定義放到cpp文件裏。這時你會發現,編譯經過而且能正常運行。ci
問題來了,爲何顯示聲明析構函數並將其定義挪到cpp裏,這個問題就解決了呢?
仍是來一段標準裏的話吧:
12.4/4
If a class has no user-declared destructor, a destructor is implicitly declared as defaulted(8.4). An implicitly declared destructor is an inline public member of its class.
因此這個隱式的inline析構函數在調用智能指針的析構函數析構管理的指針對象時,須要知道該對象的大小。而此時只能看到前置聲明而沒法看到定義也就無從知道大小,只能GG了。
1 #pragma once 2 3 struct base 4 { 5 int x; 6 };
1 #pragma once 2 3 #include <memory> 4 5 struct base; 6 struct test 7 { 8 std::unique_ptr<base> base_; 9 10 void print_base() const; 11 };
1 #include "main.h" 2 #include "base.h" 3 4 #include <cstdio> 5 6 void 7 test::print_base() const 8 { 9 std::printf("%d\n", base_->x); 10 }
1 #include "test.h" 2 3 int main() 4 { 5 test t; 6 t.print_base(); 7 8 return 0; 9 }