1. 程式人生 > >智能指針和前置聲明之間的小問題

智能指針和前置聲明之間的小問題

2.4 anti c++工程 智能 test 無法 sizeof pri inline

對比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 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;

看到這裏,還是要感謝下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

智能指針和前置聲明之間的小問題