概念
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
运行

浙公网安备 33010602011771号