4-LVI-SAM源码分析体会_1

2021SC@SDUSC

4-LVI-SAM源码分析体会_1

​ 这一篇分析体会是在看imageProjection.cpp的时候产生的

文章目录

  • 4-LVI-SAM源码分析体会_1
    • 问题与解答
    • C++体会
      • 1. 模板函数
      • 2. 函数传参
      • 3. 宏

问题与解答

  1. 每个cpp文件里面的节点初始化的节点名称一样,与节点名称唯一矛盾,为什么?

    现象如下:

    这是imageProjection.cpp的main函数:

    int main(int argc, char** argv)
    {
        ros::init(argc, argv, "lidar");
    
        ImageProjection IP;
        
        ROS_INFO("\033[1;32m----> Lidar Cloud Deskew Started.\033[0m");
    
        ros::MultiThreadedSpinner spinner(3);
        spinner.spin();
        
        return 0;
    }
    

    这是featureExtraction.cpp的main函数:

    int main(int argc, char** argv)
    {
        ros::init(argc, argv, "lidar");
    
        FeatureExtraction FE;
    
        ROS_INFO("\033[1;32m----> Lidar Feature Extraction Started.\033[0m");
       
        ros::spin();
    
        return 0;
    }
    

    ​ 经过对比,可以发现它们的节点初始化函数传进去的节点名称是一样的都是"lidar",而且,这两个文件是同时运行的。而且,在运行的时候,使用rqt_graph打印节点时,也没有出现lidar。于是带着这个疑问,我仔细地思考整个ros运行地流程,并且查阅网上的资料。我发现,网上虽然也是用ros::init进行节点初始化的,参数也包含了节点的名称,但是,在运行编译后的节点使用rosrun启动的,因此,用rqt_graph打印出来的就是ros::init里面的名字参数。

    ​ 而我们使用lvi-sam是直接用roslaunch去启动节点,而在比赛的时候,我也稍微学习了roslaunch的用法,里面是有自己定义节点的各个属性,其中包括了名称。因此,我找到了启动lidar_odometry里面4个cpp文件对应的launch文件,找到了答案。

    <launch>
    
        <arg name="project" default="lvi_sam"/>
        
        <rosparam file="$(find lvi_sam)/config/params_lidar.yaml" command="load" />
        
        <param name="vins_config_file" type="string" value="$(find lvi_sam)/config/params_camera.yaml" />
        
        
        <node pkg="$(arg project)"      type="$(arg project)_imuPreintegration"   name="$(arg project)_imuPreintegration"    output="screen"     respawn="true"/>
        <node pkg="$(arg project)"      type="$(arg project)_imageProjection"     name="$(arg project)_imageProjection"      output="screen"     respawn="true"/>
        <node pkg="$(arg project)"      type="$(arg project)_featureExtraction"   name="$(arg project)_featureExtraction"    output="screen"     respawn="true"/>
        <node pkg="$(arg project)"      type="$(arg project)_mapOptmization"      name="$(arg project)_mapOptmization"       output="screen"     respawn="true"/>
    
        
        <node pkg="$(arg project)"      type="$(arg project)_visual_feature"      name="$(arg project)_visual_feature"       output="screen"     respawn="true"/>
        <node pkg="$(arg project)"      type="$(arg project)_visual_odometry"     name="$(arg project)_visual_odometry"      output="screen"     respawn="true"/>
        <node pkg="$(arg project)"      type="$(arg project)_visual_loop"         name="$(arg project)_visual_loop"          output="screen"     respawn="true"/>
    
        
        <node pkg="image_transport" type="republish" name="$(arg project)_republish" args="compressed in:=/camera/image_raw raw out:=/camera/image_raw" output="screen" respawn="true"/>
    
    launch>
    

    ​ 可以看到,launch文件中,已经定义好了节点的名称,而这个名称,在rqt_graph也能找到。不过,在这里,我们可以又多问几个为什么。

    • roslaunch和rosrun的有什么不同?
    • 如果用roslaunch,那main函数中的ros::init还需要吗?或者,还需要main函数吗?

    ​ 为了搞清楚,roslaunch和rosrun有什么不同,我翻阅了一下wiki。roslaunch允许在本地和远程启动多个节点,并且可以在参数服务器上设置参数。而rosrun就是很简单的允许你运行一个可执行文件。显然,是rosrun运行是直接运行可执行文件,会直接从main函数进入,然后就执行了init函数,其中的名称参数也就成了节点的名称;而roslaunch应该也和rosrun在一开始运行节点是一样的,也是从main函数进入,但是,其中设置参数这个步骤会把init时设置的名称参数覆盖,因此,只要launch文件中的节点参数设置不重复,用roslaunch启动的节点名称也就不会重复。

    ​ 至于第二个问题,按照我上面的推论,应该还是需要ros::init以及main函数的,毕竟这是让系统运行代码,创建进程的一个入口(笑+摊手)。

C++体会

1. 模板函数

​ 在一开始看LVI-SAM的源代码的时候,我被C++的第一个语法就迷惑了:

subImu = nh.subscribe<sensor_msgs::Imu>(imuTopic, 2000, &ImageProjection::imuHandler, this, ros::TransportHints().tcpNoDelay());

​ 这里的函数名后面跟着一个尖括号。按照我对这个语句的理解,我可以推测这里尖括号里面的应该是消息的内容,但是为什么这里的语法会这样写呢?

​ 于是,我去ros官网,找了ros::NodeHandle这个类的源代码,subscribe这个函数有很多重载的版本,下面是上面的代码的参数对应的版本:

template<class M, class T>
Subscriber subscribe(const std::string& topic, uint32_t queue_size, void(T::*fp)(M), T* obj, 
                     const TransportHints& transport_hints = TransportHints())
{
  SubscribeOptions ops;
  ops.template initByFullCallbackType<M>(topic, queue_size, boost::bind(fp, obj, _1));
  ops.transport_hints = transport_hints;
  return subscribe(ops);
}

​ 因此,我可以进一步推测,这个尖括号的写法应该是和模板函数有关,但是问题又来了,我们平常的模板函数的调用应该是和下面的代码一样的呀?

#include 
using namespace std;
template<class T>
void Swap(T & x, T & y)
{
    T tmp = x;
    x = y;
    y = tmp;
}
int main()
{
    int n = 1, m = 2;
    Swap(n, m);  //编译器自动生成 void Swap (int &, int &)函数
    double f = 1.2, g = 2.3;
    Swap(f, g);  //编译器自动生成 void Swap (double &, double &)函数
    return 0;
}

​ 在我查阅网上的资料后,我发现,上面的代码这样写:调用模板函数和调用普通的函数没区别,是因为传递参数的时候已经指定了模板变量T的类型,而如果没有指定类型的话,需要在函数调用的时候,函数名后面写上。我写了小例子如下,里面包含了上述的两种情况:

#include
using namespace std;

template<class M, class T> // 不知道M的类型,需要显示指定
void f(int a, T b) {
    cout << "result is " << (a+b) << endl;
    M c = 'a'; // char转int
    cout << c << endl;
}
int main()
{
    int a = 1;
    f<int>(a, a); // 显式指定M的类型为int
}

下面是运行结果:
4-LVI-SAM源码分析体会_1_第1张图片

2. 函数传参

​ 在上面的例子中,我们可以看到这个ros的源代码的参数列表有个不太寻常的地方:void(T::*fp)(M)这种参数的写法其实就是把函数当成参数进行传递。对于这个机制,我写稍微写了个代码帮助理解:

#include
using namespace std;

int f(int a, int b) {
    return a+b;
}

int g(int a, int b, int(*t)(int x, int y)) {
    return t(a, b);
}

int main()
{
    printf("the result is %d;\n", g(1, 2, f));
}

运行结果如下:

4-LVI-SAM源码分析体会_1_第2张图片

​ 我们可以稍微深入一点,加入我们写的函数参数的返回值不一样会怎么样?如下:

#include
using namespace std;

int f(int a, int b) {
    return a+b;
}

int g(int a, int b, double(*t)(int x, int y)) {
    return t(a, b);
}

int main()
{
    printf("the result is %d;\n", g(1, 2, f));
}

可以看到,编译的时候会报错:

在这里插入图片描述

如果修改函数签名,也是如此,因此,如果函数要作为参数去传递的话,函数签名和返回值要完全相同才行。

3. 宏

这算是比较基础的知识,当c++里面用了宏的时候,可以不用使用分号';',因为这算是字符的替换。如下:

#define MAX A();

class A {
    MAX
};
int main() {
    A a;
    return 0;
}

你可能感兴趣的:(LVI-SAM分析,自动驾驶,c++)