避免多次含括的方法

當程式碼的長度多到一個程度時,我們會考慮將程式碼依其類別、功能或什麼什麼的,分割為多個檔案。這在程式設計中是一件很平常的事,但是對初學者而言,因為在學校裡,或是在練習教科書的範例時,往往因為程式碼並不複雜,只需要一個檔案就搞定。於是在開始進行分割時,就會遇到一堆沒遇過的錯誤。最常見的就是multiple definition,多重定義的問題。

考慮下面這個函式:

因為我們用到obj_A這個類別,假設obj_A的類別宣告在objA.h之中,那我們當然需要去含括(include) objA.h 這個檔案。但是如果obj_A定義如下,

很明顯的,我們在obj_A中使用了obj_B這個類別,假設obj_B是宣告在objB.h,那表示我們需要在objA.h中含括objB.h,不然就等著編譯器告訴你它不知道objB是什麼鬼。

好吧,那我們把objB.h也含括進去,改寫為

什麼時候會發生問題呢? 假設現在我們的foo改寫成foo2,並且存在foo2.cpp:

它同時使用了obj_A與obj_B,所以我們含括了兩個標頭檔,objA.h與objB.h。但是,因為objA.h中己經含括了objB.h,所以obj_B會被定義兩次,第一次是在處理 #include “objA.h”的時候,另一次則是在處理foo2.cpp時。

有一種簡單的解決方法,用#ifndef、#define與#endif。

當objA.h第一次被處理時,OBJ_A這個東西並沒有被「定義」,所以#ifndef (if Not defined)的結果是true,下一行就以#define去定義OBJ_A,再來則是obj_A的宣告。當objA.h第二次被處理時,OBJ_A己經被定義了,所以根本什麼事也不會做,就不會重覆處理到#include “objB.h”,就不會有重覆定義的問題,當然,objB.h裡面也要用同樣的方式來處理。

對C/C++來說,只要你的標頭檔有動過,即使對某個cpp檔完全沒有影響,但只要這個cpp檔有include這個標頭檔,這個cpp檔就要被重新編譯,想像一下一個超大的標頭檔,跟數百個cpp檔,當你修改了一個類別中的一行,數百個cpp檔全部重編是什麼感覺。

解決的方式有點複雜,牽涉到許多的改寫,在Meyers的聖經書「Effective C++」中,第31條寫到 Minimize compilation dependencies between files,候捷翻譯的中文版是寫「將檔案間的編譯依存關係降到最低」,就是在講這個問題。

Meyers書中的解法是利用一個指標pimpl (pointer to implementation)來分離介面與實作,如果你在類別中使用到其它的類別,沒關係,你把會牽涉到其它類別的實作都立到另一個地方去,也就是定義式的標頭檔,於是你必須為宣告式與定義式各提供一份標頭檔。

這種設計原則是將你原來的類別宣告轉化為handle class或是interface class,儘量的將實作與宣告分開。對初學者來講,在開始練習分割程式碼為多個檔案時,就練習這種方法,有一種大砲打小鳥的感覺,但是在有一點程度之後,還是需要練習如何處理這種事情,或者至少,你要看懂別人寫interface class或handle class是為了什麼。

註: interface class如其名,就只是介面,又稱為abstract base class,通常不帶成員,但是會有virtual的解構式,來讓別人繼承。

 

Leave a Reply

你的電子郵件位址並不會被公開。 必要欄位標記為 *