智能指針和前置聲明之間的小問題
對比Go等其他語言的工程,C++工程讓人痛苦的一件事情就是當工程稍微龐大一點,編譯時間就蹭蹭蹭往上爬。一般來說看過Effective C++這本書或者其他類似書籍的人都知道要解決編譯時長的問題,就要解決好和頭文件之間的依賴關系。所以在任何必要的時候要首先考慮使用前置聲明而不是之間include頭文件。也就是說,在定義類的時候成員變量如果是自定義類型,可以考慮將其聲明為指針類型或者是配合智能指針。函數傳參時也是一樣,使用指針或者引用。
對於一個C工程來說,因為沒有智能指針和引用的概念,所以都是直接使用指針配合前置聲明。用起來得心應手。
但是C++工程裏,有時候為了方便和省心,更多時候指針類型的成員變量會使用智能指針包一下。這個時候有可能會出現編譯通不過的情況:
MSVC:
error C2338: can‘t delete an incomplete type
warning C4150: deletion of pointer to incomplete type ‘base‘;Clang:
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 herethis->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;
看到這裏,還是要感謝下clang的輸出,比較清楚地把問題的本質原因找出來了。但是等等,我哪裏調用了智能指針的析構函數?
稍微有點警覺的情況下,你應該反應過來是默認的析構函數在做析構智能指針這事情。
我們先來做一個嘗試,把默認的析構函數顯示寫出來,然後按習慣把析構函數的定義放到cpp文件裏。這時你會發現,編譯通過並且能正常運行。
問題來了,為什麽顯示聲明析構函數並將其定義挪到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 };base.h
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 };test.h
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 }test.cpp
1 #include "test.h" 2 3 int main() 4 { 5 test t; 6 t.print_base(); 7 8 return 0; 9 }main.cpp
智能指針和前置聲明之間的小問題