解读NodeHandle(一)构造函数与析构函数

ros::NodeHandle构造函数会调用ros::start(), 最后一个ros::NodeHandle销毁时,将调用ros::shutdown()。如果想自定义节点生存期,可以用这两个函数。

检查关闭节点的两种方法是ros::ok()ros::isShuttingDown()

NodeHandle源码

NodeHandle类是非常重要的一个类,可以说是ROS程序的核心,发布、订阅就是它完成的,分析一下它的源码。

NodeHandle类中有几个私有成员变量:

1
2
3
4
5
std::string namespace_;
// 回调接口指针,主要用于advertise, subscribe,advertiseService和createTimer函数
CallbackQueueInterface* callback_queue_;

NodeHandleBackingCollection* collection_;

构造函数

NodeHandle的构造函数调用层级如下:

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
NodeHandle::NodeHandle(const std::string& ns, const M_string& remappings)
: namespace_(this_node::getNamespace())
, callback_queue_(0) // 空指针
, collection_(0) // 空指针
{
std::string  tilde_resolved_ns;
if (!ns.empty() && ns[0] == '~')// starts with tilde
tilde_resolved_ns = names::resolve(ns);
else
tilde_resolved_ns = ns;

construct(tilde_resolved_ns, true);

initRemappings(remappings);
}

getNamespace和ros::names::resolve

首先看初始化时的getNamespace,其原型是

1
2
const std::string & ros::this_node::getNamespace()	
{ return namespace_; } //Returns the namespace of the current node.

返回当前节点的namespace。比如节点初始化和启动是这样实现的:
1
2
ros::init(argc,argv,"locateTag");
ros::NodeHandle n("~node");

那么getNamespace()的结果就是/locateTag/node,但是节点名称有命名规范,否则编译正确但运行会报错:
1
2
3
terminate called after throwing an instance of 'ros::InvalidNameException'
what(): Character [-] at element [6] is not valid in Graph Resource Name [health---Status]. Valid characters are a-z, A-Z, 0-9, / and _.
已放弃 (核心已转储)

接下来的names::resolve是对参数ns进行处理。如果ns不为空而且以~开头,使用resolve函数解析,还以上面的例子,解析结果仍然是/locateTag/node,否则直接赋给tilde_resolved_nsresolve函数返回的叫做Graph Resource Names,这是ROS中的继承性命名系统,命名必须符合下面特征:

  1. 第一个字符只能是a-z|A-Z或者~ /
  2. 之后的字符是0-9|a-z|A-Z,_ /
  3. 基本名称不能有 / ~

上面说了这么多,其实在使用时,一般ns都是空,此时的getNamespace()resolveName都返回/

construct函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
void NodeHandle::construct(const std::string& ns, bool validate_name)
{
if (!ros::isInitialized()) // 若没有调用 ros::init,报错: 必须先init()然后创建NodeHandle
{
ROS_FATAL("You must call ros::init() before creating the first NodeHandle");
ROS_BREAK();
}
collection_ = new NodeHandleBackingCollection;
unresolved_namespace_ = ns;
// if callback_queue_ is nonnull, we are in a non-nullary constructor

if (validate_name)
namespace_ = resolveName(ns, true);
else
namespace_ = resolveName(ns, true, no_validate());

ok_ = true;
boost::mutex::scoped_lock lock(g_nh_refcount_mutex);
//此参数为全局变量,初始为0
if (g_nh_refcount == 0 && !ros::isStarted())
{
g_node_started_by_nh = true;
ros::start(); // 启动ros
}
++g_nh_refcount;
}

若此时没有调用ros::init,报严重错误然后终止,所以 ros::init()必须在NodeHandle创建之前。 有时先调用了ros::init也会出这个错误,很可能是在NodeHandle之前先用了ROS的东西

然后创建NodeHandleBackingCollection指针,下面是一些赋值,g_nh_refcount为初始为0的全局变量,即全局引用计数,如果此时没有启动ros,将调用ros::start(),然后将g_nh_refcount加1

ros::start()太复杂了,它是ROS架构的核心,在另一篇文章分析

initRemappings比较简单,而且不重要,就不分析了

析构函数

类的析构函数只调用了destruct():

1
2
3
4
5
6
7
8
9
10
11
12
13
void NodeHandle::destruct()
{
delete collection_;

boost::mutex::scoped_lock lock(g_nh_refcount_mutex);

--g_nh_refcount;

if (g_nh_refcount == 0 && g_node_started_by_nh)
{
ros::shutdown();
}
}

析构函数更简单,释放collection_,引用计数减1,如果变成了0,就关闭ros


参考:
node_handle.cpp源码
ROS Names
node_handle.h源码