深入解析ROS 2的通用订阅者创建:create_generic_subscription
函数逐行解析
概述
在 ROS 2 中,订阅者是节点用于接收消息的重要组件。与特定消息类型的订阅者不同,通用订阅者 (GenericSubscription
) 允许在运行时动态指定消息类型和处理回调函数。本文将详细解析 create_generic_subscription
函数的实现,逐行解释其工作原理,并展示如何在 ROS 2 中创建一个通用订阅者。
源码
首先,让我们看一下 create_generic_subscription
函数的完整源代码:
// Copyright 2020, Apex.AI Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef RCLCPP__CREATE_GENERIC_SUBSCRIPTION_HPP_
#define RCLCPP__CREATE_GENERIC_SUBSCRIPTION_HPP_
#include <functional>
#include <memory>
#include <string>
#include <utility>
#include "rcl/subscription.h"
#include "rclcpp/generic_subscription.hpp"
#include "rclcpp/node_interfaces/node_topics_interface.hpp"
#include "rclcpp/qos.hpp"
#include "rclcpp/serialized_message.hpp"
#include "rclcpp/subscription_options.hpp"
#include "rclcpp/typesupport_helpers.hpp"
namespace rclcpp
{
/// Create and return a GenericSubscription.
/**
* The returned pointer will never be empty, but this function can throw various exceptions, for
* instance when the message's package can not be found on the AMENT_PREFIX_PATH.
*
* \param topics_interface NodeTopicsInterface pointer used in parts of the setup.
* \param topic_name Topic name
* \param topic_type Topic type
* \param qos %QoS settings
* \param callback Callback for new messages of serialized form
* \param options %Publisher options.
* Not all publisher options are currently respected, the only relevant options for this
* publisher are `event_callbacks`, `use_default_callbacks`, and `%callback_group`.
*/
template<
typename CallbackT,
typename AllocatorT = std::allocator<void>>
std::shared_ptr<GenericSubscription> create_generic_subscription(
rclcpp::node_interfaces::NodeTopicsInterface::SharedPtr topics_interface,
const std::string & topic_name,
const std::string & topic_type,
const rclcpp::QoS & qos,
CallbackT && callback,
const rclcpp::SubscriptionOptionsWithAllocator<AllocatorT> & options = (
rclcpp::SubscriptionOptionsWithAllocator<AllocatorT>()
)
)
{
auto ts_lib = rclcpp::get_typesupport_library(
topic_type, "rosidl_typesupport_cpp");
auto allocator = options.get_allocator();
using rclcpp::AnySubscriptionCallback;
AnySubscriptionCallback<rclcpp::SerializedMessage, AllocatorT>
any_subscription_callback(*allocator);
any_subscription_callback.set(std::forward<CallbackT>(callback));
auto subscription = std::make_shared<GenericSubscription>(
topics_interface->get_node_base_interface(),
std::move(ts_lib),
topic_name,
topic_type,
qos,
any_subscription_callback,
options);
topics_interface->add_subscription(subscription, options.callback_group);
return subscription;
}
} // namespace rclcpp
#endif // RCLCPP__CREATE_GENERIC_SUBSCRIPTION_HPP_
逐行解析
文件头部
// Copyright 2020, Apex.AI Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
文件头部包含了版权声明和许可证信息。这部分代码明确了该代码的使用受 Apache License 2.0 约束,允许用户在遵循许可证的前提下使用、修改和分发代码。
头文件保护
#ifndef RCLCPP__CREATE_GENERIC_SUBSCRIPTION_HPP_
#define RCLCPP__CREATE_GENERIC_SUBSCRIPTION_HPP_
这是一个常见的 C++ 头文件保护机制,防止头文件被多次包含。#ifndef
宏检查 RCLCPP__CREATE_GENERIC_SUBSCRIPTION_HPP_
是否已经定义,如果没有定义,则继续定义它并包含文件内容。
包含的头文件
#include <functional>
#include <memory>
#include <string>
#include <utility>
#include "rcl/subscription.h"
#include "rclcpp/generic_subscription.hpp"
#include "rclcpp/node_interfaces/node_topics_interface.hpp"
#include "rclcpp/qos.hpp"
#include "rclcpp/serialized_message.hpp"
#include "rclcpp/subscription_options.hpp"
#include "rclcpp/typesupport_helpers.hpp"
这里包含了创建通用订阅者所需的基础设施:
<functional>
:用于处理回调函数和函数对象。<memory>
:支持智能指针管理。<string>
:支持标准字符串类型。<utility>
:提供了std::move
等实用函数。rclcpp
相关头文件:这些头文件为 ROS 2 的订阅者、节点接口、QoS 设置、序列化消息以及类型支持库提供了定义和功能支持。
命名空间声明
namespace rclcpp
{
这行代码声明了 rclcpp
命名空间。rclcpp
是 ROS 2 的核心命名空间,包含了与客户端、服务、节点、发布者、订阅者等相关的功能。
create_generic_subscription
函数
/// Create and return a GenericSubscription.
/**
* The returned pointer will never be empty, but this function can throw various exceptions, for
* instance when the message's package can not be found on the AMENT_PREFIX_PATH.
*
* \param topics_interface NodeTopicsInterface pointer used in parts of the setup.
* \param topic_name Topic name
* \param topic_type Topic type
* \param qos %QoS settings
* \param callback Callback for new messages of serialized form
* \param options %Publisher options.
* Not all publisher options are currently respected, the only relevant options for this
* publisher are `event_callbacks`, `use_default_callbacks`, and `%callback_group`.
*/
函数头解析
-
函数声明:
- 该函数是一个模板函数,接受一个回调类型
CallbackT
和一个分配器类型AllocatorT
(默认为std::allocator<void>
)。 - 返回类型为
std::shared_ptr<GenericSubscription>
,即一个指向GenericSubscription
对象的智能指针。
- 该函数是一个模板函数,接受一个回调类型
-
参数解析:
topics_interface
: 指向NodeTopicsInterface
的共享指针,用于管理节点与话题相关的接口操作。topic_name
: 要订阅的话题名称。topic_type
: 订阅的消息类型(以字符串形式表示,例如"std_msgs/msg/String"
)。qos
: 质量服务设置(QoS),控制消息的接收行为,如可靠性、历史记录等。callback
: 处理接收到的序列化消息的回调函数。options
: 订阅者选项,可以指定回调组、事件回调等配置。
函数实现
{
auto ts_lib = rclcpp::get_typesupport_library(
topic_type, "rosidl_typesupport_cpp");
- 解释:
- 通过调用
rclcpp::get_typesupport_library
函数,根据指定的topic_type
和类型支持标识符"rosidl_typesupport_cpp"
来获取消息类型的类型支持库。
- 通过调用
auto allocator = options.get_allocator();
- 解释:
- 获取
options
中指定的分配器对象,用于后续的内存管理。
- 获取
using rclcpp::AnySubscriptionCallback;
AnySubscriptionCallback<rclcpp::SerializedMessage, AllocatorT>
any_subscription_callback(*allocator);
any_subscription_callback.set(std::forward<CallbackT>(callback));
- 解释:
- 这里使用了
AnySubscriptionCallback
模板类,定义了一个通用的回调对象any_subscription_callback
,它能够处理序列化消息rclcpp::SerializedMessage
。 - 使用分配器对象初始化回调对象,并将传入的回调函数通过
set
方法设置到any_subscription_callback
中。 std::forward<CallbackT>(callback)
保留了传入回调函数的值类别(左值或右值),
- 这里使用了
auto subscription = std::make_shared<GenericSubscription>(
topics_interface->get_node_base_interface(),
std::move(ts_lib),
topic_name,
topic_type,
qos,
any_subscription_callback,
options);
- 解释:
- 使用
std::make_shared
创建一个GenericSubscription
对象的共享指针subscription
。 GenericSubscription
是 ROS 2 中用于处理任意消息类型的通用订阅者。构造函数中传入了以下参数:topics_interface->get_node_base_interface()
:获取节点的基础接口。std::move(ts_lib)
:传递类型支持库的所有权给订阅者对象。topic_name
和topic_type
:订阅的话题名称和消息类型。qos
:质量服务设置,用于控制消息传递的行为。any_subscription_callback
:设置的通用回调对象,用于处理接收到的消息。options
:订阅者选项,包含回调组和其他配置。
- 使用
topics_interface->add_subscription(subscription, options.callback_group);
- 解释:
- 将创建的
subscription
对象添加到topics_interface
中进行管理。topics_interface->add_subscription
方法将订阅者对象与特定的回调组关联,以便在接收到消息时,能够正确调度回调函数。 options.callback_group
用于指定订阅者的回调组,如果未设置则使用节点的默认回调组。
- 将创建的
return subscription;
}
- 解释:
- 该函数返回创建的
GenericSubscription
对象的共享指针subscription
。这个指针由调用方持有和管理,确保在其生命周期内订阅者对象不会被销毁。 - 返回的
GenericSubscription
对象允许在运行时动态地订阅任意类型的消息,极大地提高了系统的灵活性。
- 该函数返回创建的
结束 rclcpp
命名空间与头文件保护
} // namespace rclcpp
#endif // RCLCPP__CREATE_GENERIC_SUBSCRIPTION_HPP_
- 解释:
- 这部分代码关闭了
rclcpp
命名空间,并结束了头文件的防重复包含保护。
- 这部分代码关闭了
总结
create_generic_subscription
函数为 ROS 2 提供了一种在运行时动态创建订阅者的机制。与特定消息类型的订阅者不同,通用订阅者 (GenericSubscription
) 允许开发者在运行时指定消息类型和回调函数,从而提高了系统的灵活性和扩展性。函数通过使用 rclcpp::get_typesupport_library
来获取类型支持库,并结合 GenericSubscription
对象和 AnySubscriptionCallback
,创建一个强大的工具,满足动态消息订阅的需求。
在 ROS 2 的开发中,理解和使用 create_generic_subscription
函数可以大大简化处理不同消息类型的工作,尤其是在需要处理动态消息类型的复杂应用中。这种通用化的设计使得代码更易于维护和扩展,对于开发者来说是一个非常有用的工具。