經常有C++開發的小夥伴提問:
C++中要使用類A時,什麼時候#include "a.h",什麼時候用class A前置聲明呢?
通常來說,你都不需要主動去寫class A這種前置聲明。include能編譯通過的時候都不要去寫前置聲明,應該僅當出現了頭文件循環依賴導致編譯失敗的時候,纔去考慮去寫前置聲明!
頭文件循環依賴,就是說兩個頭文件互相include了對方,這樣編譯會出問題。舉個例子。
有a.h(裏面用了類型B的指針,所以include了b.h):
#pragma once #include "b.h" class A { public: A():_b(nullptr) {} ~A() {} void set_b(B* b) { _b = b; } B* get_b() { return _b; } private: B* _b; };
有b.h:
#pragma once #include "a.h" class B { public: B(int i):_i(i) {} ~B() {} int i() { return _i; } void foo(A& a) { B* b = a.get_b(); b->_i += _i; } private: int _i; };
有main.cpp (包含main函數)
#include "a.h" #include "b.h" #include <iostream> using namespace std; int main() { A a; B b(3); a.set_b(&b); B b2(7); b2.foo(a); cout << a.get_b()->i() << endl; return 0; }
編譯main.cpp失敗,報錯:
./b.h:13:14: error: unknown type name 'A' void foo(A& a) { ^ 1 error generated.
修改方法,因爲a.h中只出現了類型B的指針,而未調用其成員函數或成員變量,故可以修改a.h刪除include "b.h",增加類型B的前置聲明。
#pragma once class B; // 前置聲明! class A { public: A():_b(nullptr) {} ~A() {} void set_b(B* b) { _b = b; } B* get_b() { return _b; } private: B* _b; };
編譯main.cpp通過。
當然前置聲明也不是萬能的解藥,請注意前面的加粗黑字:
因爲a.h中只出現了類型B的指針,而未調用其成員函數或成員變量,故……
換言之,如果a.h中使用了類型B的成員函數,則無法通過更改爲前置聲明的方式,來讓編譯通過。
比如:
#pragma once #include <iostream> class B; class A { public: A():_b(nullptr) {} ~A() {} void set_b(B* b) { std::cout<< b->i() << std::endl; // !使用了B的成員函數 _b = b; } B* get_b() { return _b; } private: B* _b; };
編譯報錯:
./a.h:10:22: error: member access into incomplete type 'B' std::cout<< b->i() << std::endl; ^ ./a.h:3:7: note: forward declaration of 'B' class B;
這時候只能老老實實地改代碼,重新梳理並設計類A和類B的關係!
看起來有點亂,記不住?其實不難理解,因爲對C++而言,不管是什麼指針,它的大小都是確定的。所以只要a.h中只是出現B的指針(或引用)而沒有調用其具體的成員函數,C++編譯器是可以不去在此時理解B的具體定義的(故只添加class B的聲明即可),一旦a.h中用到了B的成員函數,則不然。
以上就是本次分享的所有內容,想要了解更多歡迎前往公衆號:C語言學習聯盟,每日干貨分享