一開始在剛接觸專案時,我就對標頭檔的開頭都會有#ifndef、 #define,結尾也都有#endif這個現象感到好奇,為什麼會有這種固定的寫法呢?
查了一下,原來這種寫法有一個專有名詞 Define Guard , 是用來防止有兩個檔案include了同一個標頭檔而造成重複定義錯誤。
#ifndef _xxx_H
//如果_NAME_H已經被定義過,會直接跳到endif那一行,
//也就是會跳過中間宣告的標頭檔案內容,讓這個標頭檔不會被編譯到,也就不會有重複定義問題
#define _xxx_H
.
.
.
//標頭檔案內容
.
.
.
#endif //_xxx_H
$ git clone https://github.com/m11112089/2023_iT_CMake.git
$ cd ~/2023_iT_CMake/Day8
進入build資料夾後編譯
$ cmake ..
$ make
kai@esoc:~/2023_iT_CMake/Day8/build$ make
Consolidate compiler generated dependencies of target main
[ 25%] Building CXX object CMakeFiles/main.dir/src/main.cpp.o
In file included from /home/kai/2023_iT_CMake/Day8/include/greeting.h:3,
from /home/kai/2023_iT_CMake/Day8/src/main.cpp:2:
/home/kai/2023_iT_CMake/Day8/include/hello.h:4:7: error: redefinition of ‘class Hello’
4 | class Hello
| ^~~~~
In file included from /home/kai/2023_iT_CMake/Day8/src/main.cpp:1:
/home/kai/2023_iT_CMake/Day8/include/hello.h:4:7: note: previous definition of ‘class Hello’
4 | class Hello
| ^~~~~
make[2]: *** [CMakeFiles/main.dir/build.make:76: CMakeFiles/main.dir/src/main.cpp.o] Error 1
make[1]: *** [CMakeFiles/Makefile2:83: CMakeFiles/main.dir/all] Error 2
make: *** [Makefile:91: all] Error 2
編譯此專案會發現,編譯器報 重復定義 錯誤。這是因為在 greeting.h 檔案中已經 #include 了一次 "hello.h",而在 main.cpp 主程式又同時 #include 了 "hello.h",也就是說 Hello class 的定義被引入了 2 次,C++不允許同一個物件(Object)被重複定義。
因此在實際多檔案開發中,我們需要 Define Guard 來避免發生重複定義。
將 /include/hello.h 和 /include/greeting.h 中的註解解除掉後會發現專案能夠順利編譯了。
kai@esoc:~/2023_iT_CMake/Day8/build$ make
Consolidate compiler generated dependencies of target main
[ 25%] Building CXX object CMakeFiles/main.dir/src/main.cpp.o
[ 50%] Building CXX object CMakeFiles/main.dir/src/hello.cpp.o
[ 75%] Building CXX object CMakeFiles/main.dir/src/greeting.cpp.o
[100%] Linking CXX executable main
[100%] Built target main
一個專案最好要遵循同一套命名規則,避免錯誤的發生,可以參考這份 Google C++ Style Guid 。
所有標頭檔都要有 Define Guard 以防止重複定義。
Define Guard的格式應為:
<PROJECT>_<PATH>_<FILE>_H_
為了保證唯一性,命名規則應該基於專案來源的完整路徑,例如一個路徑為 foo/src/bar/baz.h 的標頭檔應具有以下 Define Guard :
#ifndef FOO_BAR_BAZ_H_
#define FOO_BAR_BAZ_H_
...
#endif // FOO_BAR_BAZ_H_
不過我習慣不加後面那個底線。
The Multiple-Include Optimization
Google C++ Style Guid