在比较大的一些程序中,往往会涉及到一些不同模块的调用,如果这些东西放在一个.cpp文件内,这个文件会变的特别长,因此会使用多个文件互相引用。那么如何在ROS下进行这种不同文件下的引用呢,根据最近所学,简单记录一份笔记。
1、头文件的引用
头文件的引用可以说是最常见的,很多时候都会用到,举个简单的例子:
在multiple_file_test/include/multiple_file_test/test1目录下建立一个.h文件名为file1.h:
#ifndef FILE1_H_
#define FILE1_H_
#include <memory>
namespace multiple_file_test {
namespace file1 {
class Test1 {
public:
//1、关键字 explicit 用于声明构造函数,以防止隐式类型转换和复制(或赋值)构造函数的自动创建。
//错误方式:MyClass myObject = 10; // 错误:不允许隐式转换
//正确方式:MyClass myObject(10); // 正确
//2、禁用复制构造函数和赋值操作符:在 Test1 类的示例中,复制构造函数和赋值操作符被删除(使用 = delete),这意味着编译器不会为这个类自动生成默认的复制构造函数和赋值操作符。
explicit Test1();
~Test1();
Test1(const Test1 &) = delete;
Test1 &operator=(const Test1 &) = delete;
private:
};
int function1(){
return 123;
}
}
}
#endif
上述代码是一个最简单的头文件形式,它定义了一个namespace以及一个函数function1,这个函数会返回一个int型的数字123。
然后,在src目录下,建立一个main函数调用这个函数:
#include "ros/ros.h"
#include "multiple_file_test/test1/file1.h"
namespace multiple_file_test {
namespace {
void Run() {
auto value = multiple_file_test::file1::function1();
ROS_INFO("debug");
std::cout<<"value:"<<value<<std::endl;
}
}
}
int main(int argc, char **argv)
{
ros::init(argc, argv, "multiple_file_test");
ROS_INFO("multiple_file_test_node start");
multiple_file_test::Run();
return 0;
}
这里首先引入了头文件file1.h,然后在工作空间内调用了这个头文件下的具体函数function1,并打印最终结果。接下来编译这个头文件,在cmakelist中添加以下内容:
add_executable(main src/main.cpp)
add_dependencies(main ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
target_link_libraries(main ${CERES_LIBRARIES} ${catkin_LIBRARIES})
然后编译
然后
报错了:
fatal error: multiple_file_test/test1/file1.h: 没有那个文件或目录
3 | #include "multiple_file_test/test1/file1.h"
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
compilation terminated.
上面确实是有这个文件的,但是编译的时候没有找到,所以这时候需要在cmakelist中添加头文件所在路径:
include_directories(
${PROJECT_SOURCE_DIR}/include
${catkin_INCLUDE_DIRS}
)
重新编译后就可以执行了,然后运行可以得到如下结果:
通过这种方式,可以实现在一个main函数中引用同一文件夹下的其他头文件了。
2、.cc函数的引用
除了引用某些头文件中的函数外,更多时候还需要引用其他.cc或者.cpp文件下的内容,举个例子,首先在multiple_file_test/include/multiple_file_test/test2目录下建立一个.h文件名为file2.h:
#ifndef FILE2_H_
#define FILE2_H_
#include <memory>
namespace multiple_file_test {
namespace file2 {
class Test1 {
public:
//1、关键字 explicit 用于声明构造函数,以防止隐式类型转换和复制(或赋值)构造函数的自动创建。
//错误方式:MyClass myObject = 10; // 错误:不允许隐式转换
//正确方式:MyClass myObject(10); // 正确
//2、禁用复制构造函数和赋值操作符:在 MapBuilder 类的示例中,复制构造函数和赋值操作符被删除(使用 = delete),这意味着编译器不会为这个类自动生成默认的复制构造函数和赋值操作符。
explicit Test1();
~Test1();
Test1(const Test1 &) = delete;
Test1 &operator=(const Test1 &) = delete;
private:
};
int function1();
}
}
#endif
这个代码本身与file1.h的代码基本是一致的,内容上的区别在于这里的函数function1只进行了声明但是没有函数实现。然后在同一目录下再建立一个.cc文件进行函数实现:
#include "multiple_file_test/test2/file2.h"
namespace multiple_file_test {
namespace file2 {
int function1(){
return 0;
}
}
}
这个文件补充了上面.h文件中的函数实现。同样再使用main函数调用这个函数:
#include "ros/ros.h"
#include "multiple_file_test/test1/file1.h"
#include "multiple_file_test/test2/file2.h"
namespace multiple_file_test {
namespace {
void Run() {
auto value = multiple_file_test::file1::function1();
ROS_INFO("debug");
std::cout<<"value:"<<value<<std::endl;
int value2 = multiple_file_test::file2::function1();
ROS_INFO("debug");
std::cout<<"value2:"<<value2<<std::endl;
}
}
}
int main(int argc, char **argv)
{
ros::init(argc, argv, "multiple_file_test");
ROS_INFO("multiple_file_test_node start");
multiple_file_test::Run();
return 0;
}
接下来进行编译,但是此时会报错:
CMakeFiles/main.dir/src/main.cpp.o: in function `multiple_file_test::(anonymous namespace)::Run()':
main.cpp:(.text+0x18e): undefined reference to `multiple_file_test::file2::function1()'
collect2: error: ld returned 1 exit status
这是由于主函数编译的时候只找到了头文件但是没有找到具体的函数实现。如果要找到函数实现,可以通过下述方式修改Cmakelist文件增加.cc的检索路径:
方式一、指定路径
在Cmakelist中给定确定路径,针对文件夹目录比较单一的情况下使用,例如:
file(GLOB MULTIPLE_FILE_SOURCES "include/multiple_file_test/test2/*.h" "include/multiple_file_test/test2/*.cc")
add_library(multiple_file_lib ${MULTIPLE_FILE_SOURCES})
add_executable(main src/main.cpp)
add_dependencies(main ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
target_link_libraries(main ${CERES_LIBRARIES} ${catkin_LIBRARIES} multiple_file_lib)
这个地方一共涉及了三个更改,首先是增加了file与add_library,然后是在target_link_libraries中添加了library。它的特点是file中路径必须是给定完整的,如果有多个目录的话写起来就会比较冗余。
方式二、检索路径
为了弥补上面的问题,可以采用路径检索的形式让其自动搜索。这里可以有两种不同的写法:
file(GLOB_RECURSE MULTIPLE_FILE_SOURCES
"${CMAKE_SOURCE_DIR}/multiple_file_test/*.h"
"${CMAKE_SOURCE_DIR}/multiple_file_test/*.cc")
或者:
file(GLOB_RECURSE MULTIPLE_FILE_SOURCES
"${CMAKE_CURRENT_SOURCE_DIR}/include/*.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/*.cc")
这两者的区别主要在于CMAKE_SOURCE_DIR与CMAKE_CURRENT_SOURCE_DIR。CMAKE_SOURCE_DIR与CMAKE_CURRENT_SOURCE_DIR是CMake 中两个非常重要的变量,它们定义了源代码树中的两个不同的目录,具体区别如下:
CMAKE_SOURCE_DIR:
这个变量指向包含顶级 CMakeLists.txt 文件的目录。
它是项目源代码的根目录,即你调用 cmake 命令的目录。
无论在项目中的哪个 CMakeLists.txt 文件中,CMAKE_SOURCE_DIR 总是指向同一个目录。
CMAKE_CURRENT_SOURCE_DIR:
这个变量指向当前正在处理的 CMakeLists.txt 文件所在的目录。
当你在项目的某个子目录中调用 cmake 或处理子目录中的 CMakeLists.txt 文件时,CMAKE_CURRENT_SOURCE_DIR 会根据当前文件的位置而变化。
它是相对的,并且每个子目录中的 CMakeLists.txt 文件都会有不同的值。
简单来说,CMAKE_SOURCE_DIR 是项目的根目录,而 CMAKE_CURRENT_SOURCE_DIR 是当前正在被处理的目录。
举个例子:当你需要引用项目根目录下的文件或目录时,可以使用 CMAKE_SOURCE_DIR。例如,如果你的 CMakeLists.txt 位于 src/ 目录下,而你需要包含位于项目根目录下的 config/ 目录中的文件,你可以这样做:
include_directories(${CMAKE_SOURCE_DIR}/config)
而当你需要引用当前 CMakeLists.txt 所在目录下的文件或目录时,可以使用 CMAKE_CURRENT_SOURCE_DIR。例如,如果你正在 src/ 目录下的 CMakeLists.txt 中,并且需要包含相邻 include/ 目录中的文件,你可以这样做:
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)
后续的add_library,以及在target_link_libraries中添加了library这两项是一致的,通过这种方式,可以更加灵活的索引到需要的头文件。
然后编译运行一下,可以得到输出的结果为:
可以看到其调用了两个不同的工作空间下的函数并分别得到了不同的结果。