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