c++ Cmake工程(3)平行目錄 opevslam工程模式

MKT-porter發表於2024-08-14

概念

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

  執行

相關文章