實現這一目標的常規方法是將每個模塊的源代碼放入單獨的目錄中。每個目錄可以包含模塊的所有源文件和頭文件。
每個模塊的公共頭可以放在一個單獨的公共頭中。我可能會使用從公共目錄到每個頭文件的相關模塊目錄的符號鏈接。
編譯規則只是聲明沒有模塊可能包含除公共目錄中的標題以外的其他模塊的標題。這樣做的結果是,任何模塊都不能包含來自其他模塊的頭文件 - 除了公共頭文件(因此強制執行私有障礙)。
自動防止循環依賴不是微不足道的。問題在於,您只能通過一次查看幾個源文件來確定是否存在循環依賴關係,而編譯器一次只能查看一個源文件。
考慮一對模塊,ModuleA和ModuleB,以及一個使用這兩個模塊的程序Program1。
base/include
ModuleA.h
ModuleB.h
base/ModuleA
ModuleA.h
ModuleA1.c
ModuleA2.c
base/ModuleB
ModuleB.h
ModuleB1.c
ModuleB2.c
base/Program1
Program1.c
當編譯Program1.c時,如果它包含ModuleA.h和ModuleB.h,如果它使用兩個模塊的服務是完全合法的。因此,如果ModuleB.h包含在同一個翻譯單元(TU)中,ModuleA.h就不會抱怨,如果ModuleA.h包含在同一個TU中,ModuleB.h也不會抱怨。
讓我們假設它是合法的ModuleA使用ModuleB的設施。因此,在編譯ModuleA1.c或ModuleA2.c時,包含ModuleA.h和ModuleB.h都不會有問題。
但是,爲了防止循環依賴,你必須能夠使用ModuleA.h禁止ModuleB1.c和ModuleB2.c代碼。
就我所見,唯一能做到這一點的方法是需要ModuleB的一個私有頭文件,它說「ModuleA已經包含了」,即使它不是,並且這包含在ModuleA.h之前被包括在內。
ModuleA.h的骨架將是標準格式(和ModuleB.h將是類似的):
#ifndef MODULEA_H_INCLUDED
#define MODULEA_H_INCLUDED
...contents of ModuleA.h...
#endif
現在,如果在ModuleB1.c的代碼包含:
#define MODULEA_H_INCLUDED
#include "ModuleB.h"
...if ModuleA.h is also included, it will declare nothing...
...so anything that depends on its contents will fail to compile...
這遠不是自動的。
你可以做的包括文件的分析,並需要有一個循環,少拓撲排序的依賴關係。曾經有UNIX系統一起提供必須的,從而靜態(.a
)庫可以創建包含在並不需要的重新掃描命令的目標文件的服務程序tsort
(和伴侶程序,lorder
)存檔。該ranlib
程序,並最終ar
和ld
承擔了管理單個庫的重新掃描,因此特別製作冗餘的lorder
的職責。但tsort
有更多的一般用途;它在某些系統上可用(例如MacOS X; RHEL 5 Linux)。
因此,使用依賴從GCC加tsort
跟蹤,你應該能夠檢查是否有模塊之間循環。但這必須小心處理。
可能有一些IDE或其他工具集自動處理這些東西。但通常程序員可以有足夠的紀律以避免問題 - 只要需要和模塊間的依賴關係被仔細記錄。
+1很好的回答,特別是最後一段。 – mouviciel
Jonathan,選擇什麼工具來實現您的建議解決方案? Make,CMake,Rake還是其他什麼? – thegreendroid
我對更現代的系統沒有強烈的觀點;我見過CMake讚美,但你可以使用它們中的任何一個。它可能部分取決於你最熟悉的語言。例如,如果您使用Ruby,那麼Rake很有意義。我仍然傾向於使用普通的老式Make,但是我一直這麼做......很長一段時間...所以我覺得這樣做很舒服。這是一個最低公分母解決方案,具有可移植性問題。我有時使用Autoconf來處理可移植性問題,但它有其自身的複雜性(並且大大增加了小型項目的規模)。 –