概念
1-1為什麼使用前向宣告
1 減少編譯依賴: 前向宣告允許你在類宣告中引用另一個類,而不需要完整地包含其定義。這減少了標頭檔案之間的相互依賴,有助於減少編譯時間和編譯器需要處理的內容。例如: 在這個例子中,我們只需要宣告 B 類,而不需要包含 B 類的標頭檔案,因為我們只使用了 B 類的指標。 2 避免迴圈依賴: 在兩個類相互引用的情況下(即 A 類需要 B 類,B 類需要 A 類),直接包含標頭檔案可能會導致迴圈依賴,從而引發編譯錯誤。前向宣告可以避免這個問題: 這裡,A 和 B 類的定義相互依賴,前向宣告避免了包含標頭檔案的迴圈依賴。 3 最佳化編譯時間: 前向宣告可以減少不必要的標頭檔案包含,從而減少編譯時間。如果你只需要使用指標或引用,不需要類的完整定義,可以避免包含整個類的標頭檔案,這樣編譯器只需要處理前向宣告的部分。 直接包含標頭檔案的優缺點 優點: 完整型別:直接包含標頭檔案可以在編譯時獲得類的完整定義,這對於需要完整型別資訊的操作(如建立物件、呼叫非虛擬函式等)是必要的。 缺點: 增加編譯時間:包含大量標頭檔案會增加編譯時間,因為每次編譯時都需要處理這些標頭檔案及其依賴關係。 迴圈依賴:如果兩個類相互包含標頭檔案,可能會導致編譯錯誤,稱為迴圈依賴。 不必要的依賴:如果只需要一個類的指標或引用,直接包含標頭檔案會引入不必要的依賴。 結論 前向宣告是一種有效的技術,可以幫助你管理類的依賴關係,避免迴圈依賴,並提高編譯效率。當你只需要類的指標或引用時,使用前向宣告是推薦的做法;而在需要類的完整定義時,才需要包含標頭檔案。 總之,前向宣告和直接包含標頭檔案各有其適用場景,選擇合適的策略有助於構建更高效和可維護的程式碼。
1-2包含目錄
target_include_directories
-
作用範圍:
target_include_directories
是一個目標特定命令。它僅影響指定的目標(如庫或可執行檔案),而不會影響其他目標或全域性設定。 -
用法:你可以使用
target_include_directories
為特定目標設定包含目錄。這種方法使你可以更細粒度地控制哪些目標使用哪些包含目錄。用法示例如下:
target_include_directories(MyTarget PRIVATE /path/to/include)
-
其中
MyTarget
是你定義的目標的名稱。PRIVATE
表示包含目錄僅用於當前目標的編譯,而不會影響其他目標。如果使用PUBLIC
,那麼這個目錄也會傳遞給依賴於MyTarget
的其他目標。 -
特點:
- 作用於特定目標,不會影響其他目標或全域性設定。
- 提供更好的封裝性和模組化,減少了不同目標間的相互依賴。
- 更適合大型專案,因為它可以更精確地控制包含目錄的傳遞。
1-3 防止重複編譯報錯
(1) 新增宏
#ifndef A_H #define A_H 程式碼 #endif // A_H
(2)構造程式碼分開
標頭檔案和函式實體分開h和cpp寫,如果是一些簡單的類封裝的話,還可以考慮全部寫到h檔案裡。
2具體工程
A類和B類透過指標引用彼此。
多執行緒,主函式建立,在另一個執行緒完成B類對A類的資料修改。
CMakeLists.txt
cmake_minimum_required(VERSION 3.10) # 設定最低 CMake 版本 # 專案名稱和版本 project(MyProject) set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED ON) # 新增子目錄 src就可以節省一個CMakeLists.txt #add_subdirectory(src/A) #add_subdirectory(src/B) # 新增子目錄 src也得單獨寫一個CMakeLists.txt add_subdirectory(src) add_subdirectory(example)
CMakeLists.txt
# 新增子目錄 add_subdirectory(A) add_subdirectory(B)
CMakeLists.txt
# 只需要設定編譯選項 #${CMAKE_CURRENT_SOURCE_DIR} #${PROJECT_SOURCE_DIR}/src #${PROJECT_SOURCE_DIR}/src/A #${PROJECT_SOURCE_DIR}/src/B # 由於 A.cpp 依賴於 B.h 和 A.h,不需要特別的連結或編譯選項 add_library(A_lib STATIC A.cpp) # 設定 A 庫的包含目錄 target_include_directories(A_lib PRIVATE ${PROJECT_SOURCE_DIR}/src ) target_link_libraries(A_lib PUBLIC B_lib)
A.h
#ifndef A_H #define A_H #include <mutex> // #include "B/B.h" // 包含 A 類的標頭檔案 ,cpp函式中包含了,這裡沒有包含,因為前向宣告 A 類可以減少標頭檔案互鎖引用導致編譯問題和效率下降 namespace vslam { class B; // 前向宣告 B 類 class A { public: void ModifyVariable(int value); int GetVariable() const; void SetB(B* bInstance); private: int variable = 0; // 例項變數 mutable std::mutex mtx; // 保護例項變數的互斥量,mutable 允許在 const 方法中鎖定 B* bInstance = nullptr; // 指向 B 的指標 }; } // namespace vslam #endif // A_H
A.cpp
#include "A.h" /* CMakeLists.txt中設定了包含目錄,直接訪問${PROJECT_SOURCE_DIR}/src路徑下的檔案 target_include_directories(A_lib PRIVATE ${PROJECT_SOURCE_DIR}/src ) #include "B/B.h" 等同於 #include " ${PROJECT_SOURCE_DIR}/src/B/B.h" */ #include "B/B.h" // 包含 B 類的標頭檔案 namespace vslam { void A::ModifyVariable(int value) { std::lock_guard<std::mutex> lock(mtx); variable = value; } int A::GetVariable() const { std::lock_guard<std::mutex> lock(mtx); return variable; } void A::SetB(B* bInstance) { this->bInstance = bInstance; } } // namespace vslam
CMakeLists.txt
# 只需要設定編譯選項 #${CMAKE_CURRENT_SOURCE_DIR} #${PROJECT_SOURCE_DIR}/src #${PROJECT_SOURCE_DIR}/src/A #${PROJECT_SOURCE_DIR}/src/B # 由於 B.cpp 依賴於 A.h 和 B.h,不需要特別的連結或編譯選項 add_library(B_lib STATIC B.cpp) # 設定 B 庫的包含目錄 target_include_directories(B_lib PUBLIC ${PROJECT_SOURCE_DIR}/src ) target_link_libraries(B_lib PUBLIC A_lib)
B.h
#ifndef B_H #define B_H // #include "A/A.h" // 包含 A 類的標頭檔案 ,cpp函式中包含了,這裡沒有包含,因為前向宣告 A 類可以減少標頭檔案互鎖引用導致編譯問題和效率下降 namespace vslam { class A; // 前向宣告 A 類 class B { public: void DoWork(); void SetA(A* aInstance); private: A* aInstance = nullptr; // 指向 A 的指標 }; } // namespace vslam #endif // B_H
B.cpp
#include "B.h" /* CMakeLists.txt中設定了包含目錄,直接訪問${PROJECT_SOURCE_DIR}/src路徑下的檔案 target_include_directories(A_lib PRIVATE ${PROJECT_SOURCE_DIR}/src ) #include "A/A.h" 等同於 #include " ${PROJECT_SOURCE_DIR}/src/A/A.h" */ #include "A/A.h" // 包含 A 類的標頭檔案 namespace vslam { void B::DoWork() { if (aInstance) { aInstance->ModifyVariable(42); } } void B::SetA(A* aInstance) { this->aInstance = aInstance; } } // namespace vslam
主函式
CMakeLists.txt
message(STATUS "專案根路徑: " ${PROJECT_SOURCE_DIR}) # Find pthread package find_package(Threads REQUIRED) # 建立可執行檔案 add_executable(MyExecutable main.cpp) # 設定可執行檔案的包含目錄、 target_include_directories(MyExecutable PRIVATE ${PROJECT_SOURCE_DIR}/src ) # 連結 A 和 B 庫 target_link_libraries(MyExecutable PRIVATE A_lib B_lib Threads::Threads)
main.cpp
// 相對路徑引用模式 // #include "../src/A/A.h" // #include "../src/B/B.h" /* CMakeLists.txt中設定了包含目錄,直接訪問${PROJECT_SOURCE_DIR}/src路徑下的檔案 target_include_directories(A_lib PRIVATE ${PROJECT_SOURCE_DIR}/src ) #include "A/A.h" 等同於 #include " ${PROJECT_SOURCE_DIR}/src/A/A.h" */ #include "A/A.h" #include "B/B.h" #include <thread> #include <iostream> int main() { // 建立 A 和 B 例項 vslam::A a; vslam::B b; // 設定相互引用 a.SetB(&b); b.SetA(&a); // 建立並啟動一個執行緒來執行 B 的 DoWork 方法 std::thread workerThread([&b]() { b.DoWork(); }); // 等待執行緒完成 workerThread.join(); // 輸出結果 std::cout << "Variable value in A: " << a.GetVariable() << std::endl; return 0; }
編譯
cd build cmake .. sudo make -j12
執行