[转]ROS2 源码解析与实践 - Node

转载说明: 原文链接https://floodshao.github.io/2020/03/06/ros2-源码解析与实践-Node/
感谢原作者分享!如有侵权,请联系我删除,谢谢!

1. Node定义

1.1 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

1.2 Node.hpp代码

//std::enable_shared_from_this<T> 能让一个(类型是T,且已经被shared_ptr管理)安全地生成额外的std::shared_ptr。(定义于<memory>, 所以只要继承rclcpp::Node就必须要添加memory)
class Node : public std::enable_shared_from_this<Node>
{
public:
  RCLCPP_SMART_PTR_DEFINITIONS(Node) //"rclcpp/macros.hpp" 生成指针SharedPtr

  /// Create a new node with the specified name.
  /**
   * \param[in] node_name Name of the node.
   * \param[in] options Additional options to control creation of the node.
   */
  RCLCPP_PUBLIC //"rclcpp/visibility_control.hpp"
  explicit Node( //禁止隐式转换的构造函数。
    const std::string & node_name,
    const NodeOptions & options = NodeOptions());

  /// Create a new node with the specified name.
  /**
   * \param[in] node_name Name of the node.
   * \param[in] namespace_ Namespace of the node.
   * \param[in] options Additional options to control creation of the node.
   */
  RCLCPP_PUBLIC
  explicit Node(
    const std::string & node_name,
    const std::string & namespace_,
    const NodeOptions & options = NodeOptions());

  RCLCPP_PUBLIC
  virtual ~Node();

  /// Get the name of the node.
  /** \return The name of the node. */
  RCLCPP_PUBLIC
  const char * //Node::name一旦定义就不能改变
  get_name() const; //声明const不可修改成员变量

  /// Get the namespace of the node.
  /**
   * This namespace is the "node's" namespace, and therefore is not affected
   * by any sub-namespace's that may affect entities created with this instance.
   * Use get_effective_namespace() to get the full namespace used by entities.
   *
   * \sa get_sub_namespace()
   * \sa get_effective_namespace()
   * \return The namespace of the node.
   */
  RCLCPP_PUBLIC
  const char *
  get_namespace() const;

  /// Get the fully-qualified name of the node.
  /**
   * The fully-qualified name includes the local namespace and name of the node.
   */
  RCLCPP_PUBLIC
  const char *
  get_fully_qualified_name() const;

  /// Get the logger of the node.
  /** \return The logger of the node. */
  RCLCPP_PUBLIC
  rclcpp::Logger
  get_logger() const;

  /// Create and return a callback group.
  RCLCPP_PUBLIC
  rclcpp::callback_group::CallbackGroup::SharedPtr
  create_callback_group(rclcpp::callback_group::CallbackGroupType group_type);

  /// Return the list of callback groups in the node.
  RCLCPP_PUBLIC
  const std::vector<rclcpp::callback_group::CallbackGroup::WeakPtr> &
  get_callback_groups() const;

  /// Create and return a Publisher.
  /**
   * The rclcpp::QoS has several convenient constructors, including a
   * conversion constructor for size_t, which mimics older API's that
   * allows just a string and size_t to create a publisher.
   *
   * For example, all of these cases will work:
   *
   * cpp
   * pub = node->create_publisher<MsgT>("chatter", 10);  // implicitly KeepLast
   * pub = node->create_publisher<MsgT>("chatter", QoS(10));  // implicitly KeepLast
   * pub = node->create_publisher<MsgT>("chatter", QoS(KeepLast(10)));
   * pub = node->create_publisher<MsgT>("chatter", QoS(KeepAll()));
   * pub = node->create_publisher<MsgT>("chatter", QoS(1).best_effort().volatile());
   * {
   *   rclcpp::QoS custom_qos(KeepLast(10), rmw_qos_profile_sensor_data);
   *   pub = node->create_publisher<MsgT>("chatter", custom_qos);
   * }
   * 
   *
   * The publisher options may optionally be passed as the third argument for
   * any of the above cases.
   *
   * \param[in] topic_name The topic for this publisher to publish on.
   * \param[in] qos The Quality of Service settings for the publisher.
   * \param[in] options Additional options for the created Publisher.
   * \return Shared pointer to the created publisher.
   */
  template<
    typename MessageT,
    typename AllocatorT = std::allocator<void>,
    typename PublisherT = rclcpp::Publisher<MessageT, AllocatorT>>
  std::shared_ptr<PublisherT>
  create_publisher(
    const std::string & topic_name,
    const rclcpp::QoS & qos,
    const PublisherOptionsWithAllocator<AllocatorT> & options =
    PublisherOptionsWithAllocator<AllocatorT>()
  );

//publisherT指针,定义topic_name, qos和相关options

/// Create and return a Subscription.
  /**
   * \param[in] topic_name The topic to subscribe on.
   * \param[in] callback The user-defined callback function to receive a message
   * \param[in] qos_history_depth The depth of the subscription's incoming message queue.
   * \param[in] options Additional options for the creation of the Subscription.
   * \param[in] msg_mem_strat The message memory strategy to use for allocating messages.
   * \return Shared pointer to the created subscription.
   */
  template<
    typename MessageT,
    typename CallbackT,
    typename AllocatorT = std::allocator<void>,
    typename CallbackMessageT =
    typename rclcpp::subscription_traits::has_message_type<CallbackT>::type,
    typename SubscriptionT = rclcpp::Subscription<CallbackMessageT, AllocatorT>,
    typename MessageMemoryStrategyT = rclcpp::message_memory_strategy::MessageMemoryStrategy<
      CallbackMessageT,
      AllocatorT
    >
  >
  std::shared_ptr<SubscriptionT>
  create_subscription(
    const std::string & topic_name,
    const rclcpp::QoS & qos,
    CallbackT && callback,
    const SubscriptionOptionsWithAllocator<AllocatorT> & options =
    SubscriptionOptionsWithAllocator<AllocatorT>(),
    typename MessageMemoryStrategyT::SharedPtr msg_mem_strat = (
      MessageMemoryStrategyT::create_default()
    )
  );
  
  //sub消息指针,qos,相关triger callback, options
  
 /// Create a timer.
  /**
   * \param[in] period Time interval between triggers of the callback.
   * \param[in] callback User-defined callback function.
   * \param[in] group Callback group to execute this timer's callback in.
   */
  template<typename DurationRepT = int64_t, typename DurationT = std::milli, typename CallbackT>
  typename rclcpp::WallTimer<CallbackT>::SharedPtr
  create_wall_timer(
    std::chrono::duration<DurationRepT, DurationT> period,
    CallbackT callback,
    rclcpp::callback_group::CallbackGroup::SharedPtr group = nullptr);

    //timer定时器,定时区间period,trigger callback
  
  //service和client
  /* Create and return a Client. */
  template<typename ServiceT>
  typename rclcpp::Client<ServiceT>::SharedPtr
  create_client(
    const std::string & service_name,
    const rmw_qos_profile_t & qos_profile = rmw_qos_profile_services_default,
    rclcpp::callback_group::CallbackGroup::SharedPtr group = nullptr);


 /* Create and return a Service. */
  template<typename ServiceT, typename CallbackT>
  typename rclcpp::Service<ServiceT>::SharedPtr
  create_service(
    const std::string & service_name,
    CallbackT && callback,
    const rmw_qos_profile_t & qos_profile = rmw_qos_profile_services_default,
    rclcpp::callback_group::CallbackGroup::SharedPtr group = nullptr);


//参数设置部分
  RCLCPP_PUBLIC
  const rclcpp::ParameterValue &
  declare_parameter(
    const std::string & name,
    const rclcpp::ParameterValue & default_value = rclcpp::ParameterValue(),
    const rcl_interfaces::msg::ParameterDescriptor & parameter_descriptor =
    rcl_interfaces::msg::ParameterDescriptor(),
    bool ignore_override = false);
    
//泛型编程重载函数1
template<typename ParameterT>
  auto
  declare_parameter(
    const std::string & name,
    const ParameterT & default_value,
    const rcl_interfaces::msg::ParameterDescriptor & parameter_descriptor =
    rcl_interfaces::msg::ParameterDescriptor(),
    bool ignore_override = false);

//重载2
template<typename ParameterT>
  std::vector<ParameterT>
  declare_parameters(
    const std::string & namespace_,
    const std::map<std::string, ParameterT> & parameters,
    bool ignore_overrides = false);

//重载3
template<typename ParameterT>
  std::vector<ParameterT>
  declare_parameters(
    const std::string & namespace_,
    const std::map<
      std::string,
      std::pair<ParameterT, rcl_interfaces::msg::ParameterDescriptor>
    > & parameters,
    bool ignore_overrides = false);

//删除parameter
RCLCPP_PUBLIC
void
undeclare_parameter(const std::string & name);

RCLCPP_PUBLIC
bool
has_parameter(const std::string & name) const;

RCLCPP_PUBLIC
rcl_interfaces::msg::SetParametersResult
set_parameter(const rclcpp::Parameter & parameter);

RCLCPP_PUBLIC
std::vector<rcl_interfaces::msg::SetParametersResult>
set_parameters(const std::vector<rclcpp::Parameter> & parameters);

RCLCPP_PUBLIC
  rcl_interfaces::msg::SetParametersResult
  set_parameters_atomically(const std::vector<rclcpp::Parameter> & parameters);

... //相关parameters的重载函数非常多。不列举了


//成员变量
protected:
  /// Construct a sub-node, which will extend the namespace of all entities created with it.
  /**
   * \sa create_sub_node()
   *
   * \param[in] other The node from which a new sub-node is created.
   * \param[in] sub_namespace The sub-namespace of the sub-node.
   */
  RCLCPP_PUBLIC
  Node(
    const Node & other,
    const std::string & sub_namespace);

//私有成员变量
private:
  RCLCPP_DISABLE_COPY(Node) //删除拷贝构造函数

  RCLCPP_PUBLIC
  bool
  group_in_node(callback_group::CallbackGroup::SharedPtr group);

  rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_base_;
  rclcpp::node_interfaces::NodeGraphInterface::SharedPtr node_graph_;
  rclcpp::node_interfaces::NodeLoggingInterface::SharedPtr node_logging_;
  rclcpp::node_interfaces::NodeTimersInterface::SharedPtr node_timers_;
  rclcpp::node_interfaces::NodeTopicsInterface::SharedPtr node_topics_;
  rclcpp::node_interfaces::NodeServicesInterface::SharedPtr node_services_;
  rclcpp::node_interfaces::NodeClockInterface::SharedPtr node_clock_;
  rclcpp::node_interfaces::NodeParametersInterface::SharedPtr node_parameters_;
  rclcpp::node_interfaces::NodeTimeSourceInterface::SharedPtr node_time_source_;
  rclcpp::node_interfaces::NodeWaitablesInterface::SharedPtr node_waitables_;

  const rclcpp::NodeOptions node_options_;
  const std::string sub_namespace_;
  const std::string effective_namespace_;
};

2. 相关C++11的语法:

2.1 继承自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()必须要等待构造函数将对象构造完成之后才能返回。

2.2 SharedPtr成员变量来自哪里?

出现在RCLCPP_SMART_PTR_DEFINITIONS(Node)
定义如下:

#define RCLCPP_SMART_PTR_DEFINITIONS(...) \
  RCLCPP_SHARED_PTR_DEFINITIONS(__VA_ARGS__) \
  RCLCPP_WEAK_PTR_DEFINITIONS(__VA_ARGS__) \
  RCLCPP_UNIQUE_PTR_DEFINITIONS(__VA_ARGS__)
  
#define RCLCPP_SHARED_PTR_DEFINITIONS(...) \
  __RCLCPP_SHARED_PTR_ALIAS(__VA_ARGS__) \
  __RCLCPP_MAKE_SHARED_DEFINITION(__VA_ARGS__)


#define __RCLCPP_SHARED_PTR_ALIAS(...) \
  using SharedPtr = std::shared_ptr<__VA_ARGS__>; \
  using ConstSharedPtr = std::shared_ptr<const __VA_ARGS__>;

这里都是宏定义,最终落在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对象。

2.3 using关键字

在c++11中using的三种用法:https://blog.csdn.net/shift_wwx/article/details/78742459

  1. 命名空间的使用
  2. 在子类中引用基类的成员
class 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
  1. 别名指定,类似于typedef。相比于typedef,using很容易区分一个别名(尤其是函数指针)。

2.4 删除拷贝构造函数

#define RCLCPP_DISABLE_COPY	(...)	
__VA_ARGS__(const __VA_ARGS__ &) = delete; \
  __VA_ARGS__ & operator=(const __VA_ARGS__ &) = delete;

C++11则使用delete关键字显式指示编译器不生成函数的默认版本。
https://blog.csdn.net/u012333003/article/details/25299939

2.5 __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)中进行如下定义:

#if defined _WIN32 || defined __CYGWIN__ //针对win平台
  #ifdef __GNUC__
    #define RCLCPP_EXPORT __attribute__ ((dllexport))
    #define RCLCPP_IMPORT __attribute__ ((dllimport))
  #else
    #define RCLCPP_EXPORT __declspec(dllexport)
    #define RCLCPP_IMPORT __declspec(dllimport)
  #endif
  #ifdef RCLCPP_BUILDING_LIBRARY
    #define RCLCPP_PUBLIC RCLCPP_EXPORT
  #else
    #define RCLCPP_PUBLIC RCLCPP_IMPORT
  #endif
  #define RCLCPP_PUBLIC_TYPE RCLCPP_PUBLIC
  #define RCLCPP_LOCAL
#else //针对Linux平台
  #define RCLCPP_EXPORT __attribute__ ((visibility("default"))) //设置全部可见,链接库接口暴露
  #define RCLCPP_IMPORT
  #if __GNUC__ >= 4 //编译器GCC是4.0版本以上
    #define RCLCPP_PUBLIC __attribute__ ((visibility("default")))
    #define RCLCPP_LOCAL  __attribute__ ((visibility("hidden"))) //连接库接口隐藏
  #else //GCC4.0版本一下不支持visibility control,那就只能全部暴露出去了(因为代码里应该也没有相关的static声明)
    #define RCLCPP_PUBLIC 
    #define RCLCPP_LOCAL
  #endif
  #define RCLCPP_PUBLIC_TYPE
#endif

2.6 explicit

在C++中,explicit关键字用来修饰类的构造函数,被修饰的构造函数的类,不能发生相应的隐式类型转换,只能以显示的方式进行类型转换。
也就是说如果一个构造函数被声明了explicit,那么不能隐式的转换成拷贝构造函数

class Circle 
{ 
   public: 
        Circle(double r) : R(r) {} 
        Circle(int x, int y = 0) : X(x), Y(y) {} 
        Circle(const Circle& c) : R(c.R), X(c.X), Y(c.Y) {} 
   private: 
        double R; 
        int    X; 
        int    Y; 
}; 

int _tmain(int argc, _TCHAR* argv[]) 
{ 
//发生隐式类型转换 
//编译器会将它变成如下代码 
//tmp = Circle(1.23) 
//Circle A(tmp); 
//tmp.~Circle(); 
  Circle A = 1.23;
//如果构造函数变成:
// explicit Circle(double r) : R(r) {}
// 那个上边这一句就会报错,不能隐式转化拷贝构造函数。

//注意是int型的,调用的是Circle(int x, int y = 0) 
//它虽然有2个参数,但后一个有默认值,任然能发生隐式转换 
   Circle B = 123; 
//这个算隐式调用了拷贝构造函数 
   Circle C = A; 

   return 0; 
}

3. 创建pubsub实例

3.1 创建pkg

  1. 创建workspace ~/ros2_ws/src
  2. ~/ros2_ws/src下创建pkg
ros2 pkg create --build-type ament_cmake cpp_pubsub
# 基本方法是 ros2 pkg create --build-type [cmake, ament_cmake] pkg-name
# --help 访问帮助文档
  1. ~/ros2_ws/src/cpp_pubsub下会创建pkg树
-include
-src
-CMakeLists.txt
-package.xml
  1. cpp_pubsub/src下创建文件

3.2 简单的publisher node

//published_member_function.cpp
#include <chrono>
#include <memory>

#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"

using namespace std::chrono_literals;

/* This example creates a subclass of Node and uses std::bind() to register a
* member function as a callback from the timer. */

class MinimalPublisher : public rclcpp::Node
{
  public:
    MinimalPublisher()
    : Node("minimal_publisher"), count_(0)
    {
      publisher_ = this->create_publisher<std_msgs::msg::String>("topic", 10);
      timer_ = this->create_wall_timer(
      500ms, std::bind(&MinimalPublisher::timer_callback, this));
    }

  private:
    void timer_callback()
    {
      auto message = std_msgs::msg::String();
      message.data = "Hello, world! " + std::to_string(count_++);
      RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());
      publisher_->publish(message);
    }
    rclcpp::TimerBase::SharedPtr timer_;
    rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
    size_t count_;
};

//主函数
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<MinimalPublisher>());
rclcpp::shutdown();
return 0;
}
  1. c++11中std::bind()这个方法定义在中,用来实现函数转发器,广泛用于泛型编程。看一下源码定义:
template< class F, class... Args >
/*unspecified*/ bind( F&& f, Args&&... args );
- f是可调用对象callable,(函数对象、指向函数指针、到函数引用、指向成员函数指针或指向数据成员指针)
- args是要绑定的参数列表。
  1. 注意在主函数中已经没有显示的创建node(ros1中是显示的创建node handle),而是生成一个MinimalPublisher的对象进入spin。
  2. std::make_shared(Args& …args)。调用class T的构造函数,并返回一个std::shared_ptr,同时use_count+1。

3.3 CMakeLists.txt

CMakeLists中要添加dependencies和相关的编译可执行文件

cmake_minimum_required(VERSION 3.5)
project(cpp_pubsub)

# Default to C++14
if(NOT CMAKE_CXX_STANDARD)
  set(CMAKE_CXX_STANDARD 14)
endif()

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  add_compile_options(-Wall -Wextra -Wpedantic)
endif()

find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)

add_executable(talker src/publisher_member_function.cpp)
ament_target_dependencies(talker rclcpp std_msgs)

install(TARGETS
  talker
  DESTINATION lib/${PROJECT_NAME})

ament_package()

3.4 package.xml

<exec_depend>rclcpp</exec_depend>
<exec_depend>std_msgs</exec_depend>

3.5 编译publisher node

  1. 在ros2_ws/下编译
  2. colcon build 编译ros2_ws/src下的所有pkg
  3. colcon build --packages-select <pkg—name> 编译单独的pkg
  4. 目前(2020.3)还没有提供类似于catkin clean这样的清理编译空间的命令,想要清理的话只能手动删除install, log, build文件
  5. 编译得到的可执行文件在/build/cpp_pubsub/talker,可以直接执行可执行文件,不用使用ros2 run
  6. 如果要使用ros2,和ros1一样要执行环境变量操作source ros2_ws/install/setup.bash。 查看ros2寻址的所有pkg ros2 pkg list
  7. 运行ros2 run cpp_pubsub talker

3.6 同样的subscription node

//subscribe_member_function.cpp
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
using std::placeholders::_1;

class MinimalSubscriber : public rclcpp::Node
{
  public:
    MinimalSubscriber()
    : Node("minimal_subscriber")
    {
      subscription_ = this->create_subscription<std_msgs::msg::String>(
      "topic", 10, std::bind(&MinimalSubscriber::topic_callback, this, _1));
    }

  private:
    void topic_callback(const std_msgs::msg::String::SharedPtr msg) const
    {
      RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg->data.c_str());
    }
    rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;
};

int main(int argc, char * argv[])
{
  rclcpp::init(argc, argv);
  rclcpp::spin(std::make_shared<MinimalSubscriber>());
  rclcpp::shutdown();
  return 0;
}

需要注意的是不管是publisher还是subscription的构造过程中,QoS不能省了。顶层的QoS可以理解为data stream buffer,在ros1中也有相关的定义,但是因为ros2的底层是DDS,所以QoS必须定义。

std::placeholder::_1一个占位符,为什么要使用这个?
std::bind()绑定类内成员函数:

  1. 绑定的成员函数不能有重载,bind函数只能通过函数名来识别函数
  2. 绑定类内成员函数,首先要传递对象指针this,然后通过占位符_1来添加传递的参数,这里也就是msg。
  3. bind本身是一种延迟计算的思想。
  4. 那么这里就涉及到这个callback到底什么时候在调用呢。
  5. std::bind()返回的是一个函数未指定类型的函数对象,具体返回什么类型决定于可调用函数f。
  • 5
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值