Node定义
ROS1与ROS2的定义区别
- ROS1和ROS2在功能上没有区别(提供publish,subscribe,client,service等角色功能)
- 生命周期:
- ROS1中每个node的初始化都在一个main函数中,都对应一个进程。main函数中,通过初始化一个node得到一个node handle传入自定义的node behavior构造函数来定义node行为
- ROS2中每个node都继承自一个基类rclcpp::Node,采用子类继承的方式来暴露接口,这样每个node的生命周期都得到有效的控制。(这个lifecycle定义在更底层rcl层)
- ROS2 Node提供的创建服务接口,node实例继承自基类,创建函数返回的都是SharedPtr:
- rclcpp::Node->create_callback_group
- rclcpp::Node->create_publisher
- rclcpp::Node->create_subscription
- rclcpp::Node->create_client
- rclcpp::Node->create_service
- rclcpp::Node->create_subnode
- rclcpp::Node->create_wall_timer
node.hpp
1 | //std::enable_shared_from_this<T> 能让一个(类型是T,且已经被shared_ptr管理)安全地生成额外的std::shared_ptr。(定义于<memory>, 所以只要继承rclcpp::Node就必须要添加memory) |
相关C++11的语法:
继承自std::enable_shared_from_this
https://blog.csdn.net/fm_VAE/article/details/79660768
在智能指针的使用过程中我们会遇到这样一种情况,我们在类的成员函数调用某一个函数,而该函数需要传递一个当前对象的智能指针作为参数时,我们需要能够在成员函数中获得自己的智能指针。传递这个智能指针可以增加指针的引用次数,放置在函数执行的过程中,对象被释放而引发引用错误。
但是我们不能在类中使用this构建一个这样的shared_ptr, 不是成员函数的shared_ptr是在栈中生成的,一旦超出作用域,shared_ptr被析构,会导致这个对象本身的this被析构,从而导致致命错误。
为了解决这个问题,在c++11中提供了enable_shared_from_this这个模板类。他提供了一个shared_from_this方法返回自己的智能指针。而这个返回的shared_from_this是基类成员变量,会在返回的过程中增加指针的引用次数。
注意这个shared_from_this()必须要等待构造函数将对象构造完成之后才能返回。
SharedPtr成员变量来自哪里?
出现在RCLCPP_SMART_PTR_DEFINITIONS(Node)
定义如下:
1 |
|
这里都是宏定义,最终落在using SharedPtr = std::shared_ptr<__VA_ARGS__>
等定义。在这里SharedPtr就是shared_ptr<__VA_ARGS__>
的别名。
注意这里的SharedPtr。在看代码的时候经常会发现有如下:rclcpp::Subscription<std_msgs::msg::String>::SharedPtr sub_;
你就会发现说这个类里边没有定义SharedPtr这个成员变量啊,这个是怎么来的。你去看源码的时候就会发现每一个基类都会有RCLCPP_SMART_PTR_DEFINITIONS(T)这个宏定义,找到最末端就会发现SharedPtr定义在这里,然后还是public成员变量。
这里就留了一个疑问?所有的基类都继承自std::enable_shared_from_this
,说明就提供了一个方法shared_from_this()
方法返回一个这个对象的指针。那为什么还要重新创建一个SharedPtr对象。
using关键字
在c++11中using的三种用法:
https://blog.csdn.net/shift_wwx/article/details/78742459
- 命名空间的使用
- 在子类中引用基类的成员
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18class T5Base {
public:
T5Base() :value(55) {}
virtual ~T5Base() {}
void test1() { cout << "T5Base test1..." << endl; }
protected:
int value;
};
class T5Derived : private T5Base { //私有继承,成员变量在子类中全部转为private.。那么在public中如何访问基类成员。
public:
//using T5Base::test1;
//using T5Base::value;
void test2() { cout << "value is " << value << endl; }
};
————————————————
版权声明:本文为CSDN博主「私房菜」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/shift_wwx/article/details/78742459 - 别名指定,类似于typedef。相比于typedef,using很容易区分一个别名(尤其是函数指针)。
删除拷贝构造函数
1 | #define RCLCPP_DISABLE_COPY (...) |
C++11则使用delete关键字显式指示编译器不生成函数的默认版本。
https://blog.csdn.net/u012333003/article/details/25299939
__attribute__
__attribute__可以设置函数属性(FunctionAttribute)、变量属性(Variable Attribute)和类型属性(Type Attribute). 函数属性可以帮助开发者把一些特性添加到函数声明中,从而可以使编译器在错误检查方面的功能更强大。也很容易同非GNU应用程序做到兼容之功效。
出现在RCLCPP_PUBLIC等定义中(visibility_control.hpp) https://gcc.gnu.org/wiki/Visibility
语法格式为__attribute__ ((attribute-list))
在C语言中,可以使用static关键字限制符号(函数或变量)只对当前的文件可见,即,static对符号的限制在单个文件级别。而共享库(或动态库)可能包含一个或多个文件,如何将符号限制在库文件(模块)的级别,大多数链接器提供了将一个模块的所有符号进行隐藏或导出的方法,但这样对符号的控制会缺乏灵活性,因此,还有一些额外的工作需要我们来处理。(在GCC 4.0下)
https://blog.csdn.net/zdragon2002/article/details/6061962
https://blog.csdn.net/delphiwcdj/article/details/45225889
在编译一个大型的动态链接库的时候,选择不同的链接方式-fvisibility=default
可以决定动态链接库向外暴露哪些接口。
而使用宏定义来实现visibility可以方便代码在不同平台上迁移。
所以在(visibility_control.hpp)中进行如下定义:
1 |
explicit
在C++中,explicit关键字用来修饰类的构造函数,被修饰的构造函数的类,不能发生相应的隐式类型转换,只能以显示的方式进行类型转换。
也就是说如果一个构造函数被声明了explicit,那么不能隐式的转换成拷贝构造函数。
1 | class Circle |
创建pubsub实例
创建pkg
- 创建workspace
~/ros2_ws/src
- 在
~/ros2_ws/src
下创建pkg1
2
3ros2 pkg create --build-type ament_cmake cpp_pubsub
# 基本方法是 ros2 pkg create --build-type [cmake, ament_cmake] pkg-name
# --help 访问帮助文档 - 在
~/ros2_ws/src/cpp_pubsub
下会创建pkg树1
2
3
4-include
-src
-CMakeLists.txt
-package.xml - 在
cpp_pubsub/src
下创建文件
简单的publisher node
1 | //published_member_function.cpp |
- c++11中
std::bind()
这个方法定义在中,用来实现函数转发器,广泛用于泛型编程。看一下源码定义: 1
2
3
4template< class F, class... Args >
/*unspecified*/ bind( F&& f, Args&&... args );
- f是可调用对象callable,(函数对象、指向函数指针、到函数引用、指向成员函数指针或指向数据成员指针)
- args是要绑定的参数列表。 - 注意在主函数中已经没有显示的创建node(ros1中是显示的创建node handle),而是生成一个MinimalPublisher的对象进入spin。
- std::make_shared
(Args& …args)。调用class T的构造函数,并返回一个std::shared_ptr,同时use_count+1。
CMakeLists.txt
CMakeLists中要添加dependencies和相关的编译可执行文件
1 | cmake_minimum_required(VERSION 3.5) |
package.xml
1 | <exec_depend>rclcpp</exec_depend> |
编译publisher node
- 在
ros2_ws/
下编译 colcon build
编译ros2_ws/src
下的所有pkgcolcon build --packages-select <pkg—name>
编译单独的pkg- 目前(2020.3)还没有提供类似于
catkin clean
这样的清理编译空间的命令,想要清理的话只能手动删除install, log, build文件 - 编译得到的可执行文件在
/build/cpp_pubsub/talker
,可以直接执行可执行文件,不用使用ros2 run - 如果要使用ros2,和ros1一样要执行环境变量操作
source ros2_ws/install/setup.bash
。 查看ros2寻址的所有pkgros2 pkg list
- 运行
ros2 run cpp_pubsub talker
同样的subscription node
1 | //subscribe_member_function.cpp |
需要注意的是不管是publisher还是subscription的构造过程中,QoS不能省了。顶层的QoS可以理解为data stream buffer,在ros1中也有相关的定义,但是因为ros2的底层是DDS,所以QoS必须定义。
std::placeholder::_1
一个占位符,为什么要使用这个?std::bind()
绑定类内成员函数:
- 绑定的成员函数不能有重载,bind函数只能通过函数名来识别函数
- 绑定类内成员函数,首先要传递对象指针this,然后通过占位符_1来添加传递的参数,这里也就是msg。
- bind本身是一种延迟计算的思想。
那么这里就涉及到这个callback到底什么时候在调用呢。 - std::bind()返回的是一个函数未指定类型的函数对象,具体返回什么类型决定于可调用函数f。