深入解析 ROS 2 rclcpp 的 NodeInterfaces 辅助工具
目录
在 ROS 2 的rclcpp
库中,rclcpp::node_interfaces::detail::NodeInterfacesHelpers.hpp
文件提供了一系列模板类和辅助函数,用于支持NodeInterfaces
类的实现。这些工具帮助管理和获取不同类型的节点接口,确保接口的正确初始化和使用。
一、文件概述
这个头文件主要包含了用于支持NodeInterfaces
类的模板类和辅助函数。它定义了一些通用的模板结构,用于存储节点接口,并提供了构造函数和获取接口的方法。此外,还定义了一些宏,用于简化特定节点接口的支持添加过程。
二、主要模板类和结构
0. 源码
// Copyright 2022 Open Source Robotics Foundation, 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__NODE_INTERFACES__DETAIL__NODE_INTERFACES_HELPERS_HPP_
#define RCLCPP__NODE_INTERFACES__DETAIL__NODE_INTERFACES_HELPERS_HPP_
#include <memory>
#include <tuple>
#include <type_traits>
#include <utility>
#include "rclcpp/visibility_control.hpp"
namespace rclcpp
{
namespace node_interfaces
{
namespace detail
{
// Support and Helper template classes for the NodeInterfaces class.
template<typename NodeT, typename ... Ts>
std::tuple<std::shared_ptr<Ts>...>
init_tuple(NodeT & n);
/// Stores the interfaces in a tuple, provides constructors, and getters.
template<typename ... InterfaceTs>
struct NodeInterfacesStorage
{
template<typename NodeT>
NodeInterfacesStorage(NodeT & node) // NOLINT(runtime/explicit)
: interfaces_(init_tuple<decltype(node), InterfaceTs ...>(node))
{}
NodeInterfacesStorage()
: interfaces_()
{}
explicit NodeInterfacesStorage(std::shared_ptr<InterfaceTs>... args)
: interfaces_(args ...)
{}
/// Individual Node Interface non-const getter.
template<typename NodeInterfaceT>
std::shared_ptr<NodeInterfaceT>
get()
{
static_assert(
(std::is_same_v<NodeInterfaceT, InterfaceTs>|| ...),
"NodeInterfaces class does not contain given NodeInterfaceT");
return std::get<std::shared_ptr<NodeInterfaceT>>(interfaces_);
}
/// Individual Node Interface const getter.
template<typename NodeInterfaceT>
std::shared_ptr<const NodeInterfaceT>
get() const
{
static_assert(
(std::is_same_v<NodeInterfaceT, InterfaceTs>|| ...),
"NodeInterfaces class does not contain given NodeInterfaceT");
return std::get<std::shared_ptr<NodeInterfaceT>>(interfaces_);
}
protected:
std::tuple<std::shared_ptr<InterfaceTs>...> interfaces_;
};
/// Prototype of NodeInterfacesSupports.
/**
* Should read NodeInterfacesSupports<..., T, ...> as "NodeInterfaces supports T", and
* if NodeInterfacesSupport is specialized for T, the is_supported should be
* set to std::true_type, but by default it is std::false_type, which will
* lead to a compiler error when trying to use T with NodeInterfaces.
*/
template<typename StorageClassT, typename ... Ts>
struct NodeInterfacesSupports;
/// Prototype of NodeInterfacesSupportCheck template meta-function.
/**
* This meta-function checks that all the types given are supported,
* throwing a more human-readable error if an unsupported type is used.
*/
template<typename StorageClassT, typename ... InterfaceTs>
struct NodeInterfacesSupportCheck;
/// Iterating specialization that ensures classes are supported and inherited.
template<typename StorageClassT, typename NextInterfaceT, typename ... RemainingInterfaceTs>
struct NodeInterfacesSupportCheck<StorageClassT, NextInterfaceT, RemainingInterfaceTs ...>
: public NodeInterfacesSupportCheck<StorageClassT, RemainingInterfaceTs ...>
{
static_assert(
NodeInterfacesSupports<StorageClassT, NextInterfaceT>::is_supported::value,
"given NodeInterfaceT is not supported by rclcpp::node_interfaces::NodeInterfaces");
};
/// Terminating case when there are no more "RemainingInterfaceTs".
template<typename StorageClassT>
struct NodeInterfacesSupportCheck<StorageClassT>
{};
/// Default specialization, needs to be specialized for each supported interface.
template<typename StorageClassT, typename ... RemainingInterfaceTs>
struct NodeInterfacesSupports
{
// Specializations need to set this to std::true_type in addition to other interfaces.
using is_supported = std::false_type;
};
/// Terminating specialization of NodeInterfacesSupports.
template<typename StorageClassT>
struct NodeInterfacesSupports<StorageClassT>
: public StorageClassT
{
/// Perfect forwarding constructor to get arguments down to StorageClassT.
template<typename ... ArgsT>
explicit NodeInterfacesSupports(ArgsT && ... args)
: StorageClassT(std::forward<ArgsT>(args) ...)
{}
};
// Helper functions to initialize the tuple in NodeInterfaces.
template<typename StorageClassT, typename ElementT, typename TupleT, typename NodeT>
void
init_element(TupleT & t, NodeT & n)
{
std::get<std::shared_ptr<ElementT>>(t) =
NodeInterfacesSupports<StorageClassT, ElementT>::get_from_node_like(n);
}
template<typename NodeT, typename ... Ts>
std::tuple<std::shared_ptr<Ts>...>
init_tuple(NodeT & n)
{
using StorageClassT = NodeInterfacesStorage<Ts ...>;
std::tuple<std::shared_ptr<Ts>...> t;
(init_element<StorageClassT, Ts>(t, n), ...);
return t;
}
/// Macro for creating specializations with less boilerplate.
/**
* You can use this macro to add support for your interface class if:
*
* - The standard getter is get_node_{NodeInterfaceName}_interface(), and
* - the getter returns a non-const shared_ptr<{NodeInterfaceType}>
*
* Examples of using this can be seen in the standard node interface headers
* in rclcpp, e.g. rclcpp/node_interfaces/node_base_interface.hpp has:
*
* RCLCPP_NODE_INTERFACE_HELPERS_SUPPORT(rclcpp::node_interfaces::NodeBaseInterface, base)
*
* If your interface has a non-standard getter, or you want to instrument it or
* something like that, then you'll need to create your own specialization of
* the NodeInterfacesSupports struct without this macro.
*/
// *INDENT-OFF*
#define RCLCPP_NODE_INTERFACE_HELPERS_SUPPORT(NodeInterfaceType, NodeInterfaceName) \
namespace rclcpp::node_interfaces::detail { \
template<typename StorageClassT, typename ... RemainingInterfaceTs> \
struct NodeInterfacesSupports< \
StorageClassT, \
NodeInterfaceType, \
RemainingInterfaceTs ...> \
: public NodeInterfacesSupports<StorageClassT, RemainingInterfaceTs ...> \
{ \
using is_supported = std::true_type; \
\
template<typename NodeT> \
static \
std::shared_ptr<NodeInterfaceType> \
get_from_node_like(NodeT & node_like) \
{ \
return node_like.get_node_ ## NodeInterfaceName ## _interface(); \
} \
\
/* Perfect forwarding constructor to get arguments down to StorageClassT (eventually). */ \
template<typename ... ArgsT> \
explicit NodeInterfacesSupports(ArgsT && ... args) \
: NodeInterfacesSupports<StorageClassT, RemainingInterfaceTs ...>( \
std::forward<ArgsT>(args) ...) \
{} \
\
std::shared_ptr<NodeInterfaceType> \
get_node_ ## NodeInterfaceName ## _interface() \
{ \
return StorageClassT::template get<NodeInterfaceType>(); \
} \
}; \
} // namespace rclcpp::node_interfaces::detail
// *INDENT-ON*
} // namespace detail
} // namespace node_interfaces
} // namespace rclcpp
#endif // RCLCPP__NODE_INTERFACES__DETAIL__NODE_INTERFACES_HELPERS_HPP_
1. NodeInterfacesStorage
- 用途:用于存储不同类型的节点接口指针,并提供构造函数和获取接口的方法。
- 模板参数:
InterfaceTs...
表示不同的节点接口类型。 - 成员变量:
interfaces_
:存储节点接口指针的元组。
- 构造函数:
NodeInterfacesStorage(NodeT & node)
:接受一个节点对象作为参数,通过init_tuple
函数初始化接口元组。NodeInterfacesStorage()
:默认构造函数,创建一个空的接口元组。NodeInterfacesStorage(std::shared_ptr<InterfaceTs>... args)
:接受多个接口指针作为参数,直接初始化接口元组。
- 成员函数:
template<typename NodeInterfaceT> std::shared_ptr<NodeInterfaceT> get()
:获取特定类型的节点接口指针。如果接口类型不匹配,会触发静态断言错误。template<typename NodeInterfaceT> std::shared_ptr<const NodeInterfaceT> get() const
:获取特定类型的常量节点接口指针。同样,如果接口类型不匹配,会触发静态断言错误。
2. NodeInterfacesSupports
- 用途:用于检查特定类型的节点接口是否被支持,并提供获取接口的方法。这个模板类需要针对每个支持的接口进行特化。
- 模板参数:
StorageClassT
:存储接口的类。Ts...
:表示不同的节点接口类型。
- 成员变量:
is_supported
:静态成员类型,用于表示特定接口是否被支持。默认情况下为std::false_type
,需要在特化中设置为std::true_type
。
- 成员函数:
template<typename NodeT> static std::shared_ptr<NodeInterfaceType> get_from_node_like(NodeT & node_like)
:静态方法,用于从节点对象中获取特定类型的接口指针。具体实现需要在特化中提供。- 构造函数:根据不同的特化情况,可以有不同的构造函数,用于将参数传递给基类构造函数。
std::shared_ptr<NodeInterfaceType> get_node_NodeInterfaceName_interface()
:获取特定类型的接口指针,内部调用StorageClassT::template get<NodeInterfaceType>()
。
3. NodeInterfacesSupportCheck
- 用途:用于检查一组节点接口是否都被支持。通过递归特化,确保每个接口都被正确支持,并在编译时检测不支持的接口类型。
- 模板参数:
StorageClassT
:存储接口的类。InterfaceTs...
:表示不同的节点接口类型。
- 成员变量:无
- 成员函数:无
- 实现方式:通过递归特化,首先检查第一个接口是否被支持,如果支持,则继续检查剩余的接口。如果有任何一个接口不被支持,会触发静态断言错误。
三、辅助函数
1. init_element
- 用途:用于初始化
NodeInterfacesStorage
中的特定接口元素。 - 模板参数:
StorageClassT
:存储接口的类。ElementT
:要初始化的接口类型。TupleT
:接口元组类型。NodeT
:节点类型。
- 实现方式:通过调用
NodeInterfacesSupports<StorageClassT, ElementT>::get_from_node_like
获取接口指针,并将其存储在元组中。
2. init_tuple
- 用途:用于初始化
NodeInterfacesStorage
的接口元组。 - 模板参数:
NodeT
表示节点类型,Ts...
表示不同的节点接口类型。 - 实现方式:
- 使用
using StorageClassT = NodeInterfacesStorage<Ts...>
定义存储接口的类。 - 创建一个接口元组
t
。 - 通过展开参数包调用
init_element
函数,初始化元组中的每个接口元素。 - 返回初始化后的接口元组。
- 使用
四、宏定义
RCLCPP_NODE_INTERFACE_HELPERS_SUPPORT
- 用途:用于简化特定节点接口的支持添加过程。如果接口的标准获取器是
get_node_{NodeInterfaceName}_interface()
,并且返回一个非常量的共享指针<{NodeInterfaceType}>
,可以使用这个宏来添加对该接口的支持。 - 参数:
NodeInterfaceType
:要支持的节点接口类型。NodeInterfaceName
:节点接口的名称,用于生成获取器的名称。
- 实现方式:
- 在
rclcpp::node_interfaces::detail
命名空间中定义一个特化的NodeInterfacesSupports
结构。 - 设置
is_supported
为std::true_type
,表示该接口被支持。 - 提供
get_from_node_like
静态方法,用于从节点对象中获取特定类型的接口指针,通过调用节点对象的get_node_NodeInterfaceName_interface()
方法实现。 - 提供构造函数,用于将参数传递给基类构造函数。
- 提供
get_node_NodeInterfaceName_interface()
方法,用于获取特定类型的接口指针,内部调用StorageClassT::template get<NodeInterfaceType>()
。
- 在
五、总结
rclcpp::node_interfaces::detail::NodeInterfacesHelpers.hpp
文件提供了一系列工具,用于支持NodeInterfaces
类的实现。通过这些模板类和辅助函数,可以方便地管理和获取不同类型的节点接口,确保接口的正确初始化和使用。同时,宏定义的使用可以简化特定接口的支持添加过程,提高代码的可读性和可维护性。理解这些工具的实现对于深入理解rclcpp
库中的节点接口管理机制非常重要。