前向宣告 (Forward Declarations)

盡可能地避免使用前向宣告。 只要 #include 你需要的標頭檔就好。

定義

類別、函式與模板的前向宣告指的就是在沒有定義其內容的情況下,預先宣告名稱的程式碼。

優點

  • 前向宣告可以節省編譯時間。 #include 會使得編譯器處理時必須要開啟更多檔案而且處理更多資料。
  • 前向宣告可以避免不必要的重新編譯。 #include 有可能在標頭檔做了些無關的修改時,使得其他相關的程式碼就得需要被重新編譯。

缺點

  • 前向宣告會隱藏某些依賴關係,可能會造成使用者的程式碼在標頭檔修改後略過了必要的重新編譯過程。

  • 一個前向宣告可能會因為其函式庫內部的修改而損壞。 函式與模板的前向宣告會造成撰寫標頭檔的人無法更改 API,像是增加函式的參數,給模板增加一個預設數值,或者轉移到一個新的名稱空間。

  • 前向宣告 std:: 名稱空間內的符號時常產生一些未定義的行為。

  • 有時候很難界定到底是否該在程式碼中使用前向宣告,或者全部使用 #include。 有時候替換掉 #include 可能會大幅改變程式碼的意義:

    // b.h:
    struct B {};
    struct D : B {};
    
    // good_user.cc:
    #include "b.h"
    void f(B*);
    void f(void*);
    void test(D* x) { f(x); } // calls f(B*)
    

    如果上面的程式碼中,將 #include 替換成 BD 的前向宣告的話,test() 就會呼叫 f(void*)

  • 從一個標頭檔前向宣告多個符號比單純地 #include 更難在發生錯誤時除錯。

  • 為了使用前向宣告而重構程式碼(像是把把物件成員換成指標),可能會造成程式變慢或者更加複雜。

我們的決定

  • 盡量避免前向宣告其他專案中的實體。
  • 當使用一個宣告在標頭檔內的函式時,總是 #include 那個標頭檔。
  • 當你要使用類別模板時,盡量使用 #include

請參考 #include 時的名稱與順序 來參考何時應該插入標頭檔

譯註

老實說我也是第一次看到「前向宣告」這個詞,因此我花了點時間研究這到底是什麼東西。 我稍微閱讀了維基百科上的資料之後,在此寫下我對這個這個詞的理解。

事實上,前向宣告這個詞簡單來說就是「在定義之前先宣告」。 相信應該不少人有宣告過函式吧?像是:

int sum(int, int);

這裏你可以看到我們並沒有寫說 sum 這個函式代表著什麼意思,只說他接受兩個整數參數,回傳一個整數值,而實際上 sum 的內容則定義在別的地方。 這個目的就是在告訴編譯器:「有個叫做 sum 的函式存在,等下使用時,請把 sum 這個符號當成一個接受兩個整數參數,回傳一個整數的函式處理。」

其實這就是一個「前向宣告」,也就是預先告知這個東西的存在,然後再另外定義內容。

前向宣告除了函式之外,也可以用在類別與模板上。 假設現在其他檔案中定義了 class A,而你的檔案需要用到它,你可以單純地宣告:

class A;

這樣就可以不用使用 #include 來載入相關的標頭檔。 但是這個使用有個限制,就是程式碼中使用到 class A 的地方,都只能使用指標或參考,而不能直接使用像是 A a 這樣的變數。 使用類別的前向宣告要注意的事情相對比較多,所以上面的建議才會提到大部份的情況還是直接 #include 比較好。