• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
MKT-porter
博客园    首页    新随笔    联系   管理    订阅  订阅
c++ Cmake工程(3)平行目录 opevslam工程模式

 

概念

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

  运行

 

posted on 2024-08-14 02:55  MKT-porter  阅读(48)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3