NS-3学习笔记(四):NS-3的对象框架 之 TypeId

本章主要介绍NS-3的对象框架当中的必不可少的TypeId。要使用TypeId的类,只要继承ObjectBase,然后重写TypeId即可。TypeId提供了一种动态创建对象的机制,弥补了C++的不足。使用TypeId还可以判断对象所属的继承关系。此外,TypeId还为后来的属性框架(Attribute Framework)和追踪框架(Tracing Framework)提供了必要的支持。可以想象TypeId完成了Java当中Class类所完成的某些功能,然而其功能远远没有Class类强大,并且大部分操作都是靠编译时处理的,无法像Java当中一样动态卸载和加载类型。

1. TypeId简介

C++是一种静态语言,无法动态的创建类和对象,也无法像Java一样是用反射机制从配置文件读取字符串,再通过字符串创建对象。为了弥补这种缺陷,NS-3实现了一套机制,称为TypeId,不但可以动态从字符串创建对象,还可以判断对象的类型归属。除此之外,TypeId还是NS-3的属性框架(Attribute Framework)和追踪框架(Tracing Framework)的基础。没有TypeId的支持,NS-3的对象框架将无法运作。因此在学习其他对象框架的特性之前,必须先对NS-3的TypeId有一定的了解。

TypeId可以认为是NS-3提出的一种机制,但实际上,TypeId只是NS-3类库当中的一个类。TypeId一般被需要使用NS-3对象框架的其他类创建并使用。这些类需要在一个名为GetTypeId()的方法中创建TypeId的实例,这个TypeId的实例中存储了创建它的类的一些基本信息,包括:

  • 名称空间是什么
  • 类的名字是什么
  • 父类是谁
  • 构造函数是什么(以及是否有构造函数)
  • 有哪些属性(属性框架)
  • 有哪些值的改变需要追踪(追踪系统)
  • 有哪些事件的发生需要追踪(追踪系统)
  • ……

TypeId所记录的类的名字是用字符串表示的。此后通过这个字符串就可以创建出对象的类。这在很大程度上非常类似于Java的反射机制。 然而,TypeId本身是没有任何作用的。它必须在一个类的内部定义,而这个类一般都继承自ObjectBase。因为NS-3配合ObjectBase类提供了一些非常有用的工具类。

2. ObjectBase

ObjectBase是整个NS-3对象框架的基础。为NS-3的其他对象功能提供必要的支持。首先来看看ObjectBase的代码结构:

object-base.cc
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
27
28
29
30
31
class ObjectBase
{
public:
//TypeId相关
static TypeId GetTypeId (void);
virtual TypeId GetInstanceTypeId (void) const = 0;

virtual ~ObjectBase ();

//属性框架相关
void SetAttribute (std::string name, const AttributeValue &value);
bool SetAttributeFailSafe (std::string name, const AttributeValue &value);
void GetAttribute (std::string name, AttributeValue &value) const;
bool GetAttributeFailSafe (std::string name, AttributeValue &value) const;

//追踪框架相关
bool TraceConnect (std::string name, std::string context, const CallbackBase &cb);
bool TraceConnectWithoutContext (std::string name, const CallbackBase &cb);
bool TraceDisconnect (std::string name, std::string context, const CallbackBase &cb);
bool TraceDisconnectWithoutContext (std::string name, const CallbackBase &cb);

protected:
virtual void NotifyConstructionCompleted (void);
void ConstructSelf (const AttributeConstructionList &attributes);

private:
bool DoSet (Ptr<const AttributeAccessor> spec,
Ptr<const AttributeChecker> checker,
const AttributeValue &value);

};

其中的公有方法可以大概分成三大功能模块:

  • TypeId支持
    • GetTypeId():通过类获得当前类的TypeId
    • GetInstanceTypeId():通过类的实例获得其TypeId
  • 属性框架支持
    • SetAttribute():为对象设置一个属性
    • SetAttributeFailSafe():为对象设置一个属性,出现错误的时候不会停止运行
    • GetAttribute():获取对象的某个属性
    • GetAttributeFailSafe():获取对象的某个属性,出现错误的时候不会停止程序的运行
  • 追踪框架支持
    • TraceConnect():链接对象的某个追踪源
    • TraceConnectWithoutContext():连接对象的某个追踪源,但是不携带上下文信息
    • TraceDisconnect():断开对象的某个追踪源,不再继续追踪属性的变化和事件的发生
    • TraceDisconnectWithoutContext():断开不带上下文的某个追踪源

属性系统和追踪系统会在后面章节进行介绍,本章我们先介绍TypeId。

GetTypeId()是一个静态方法,因此,可以直接通过类来调用。例如ObjectBase::GetTypeId()。而GetInstanceTypeId()是一个实例方法,并且是一个虚方法,因此调用的时候将根据实际的类型调用其子类的重写方法。除此之外,在ObjectBase当中,GetInstanceTypeId()是一个纯虚方法,这就意味着ObjectBase是一个抽象类,无法被实例化。继承ObjectBase的具体类,必须实现GetInstanceTypeId()方法并返回一个TypeId值。如果没有特殊的需求的话,GetInstanceTypeId()所返回的TypeId值,应该和GetTypeId()值一致,否则就会出现实例的TypeId和类型的TypeId不一致的情况,因此一般都是直接在GetInstanceTypeId()当中调用GetTypeId()静态方法。

3. TypeId

既然GetTypeId()和GetInstanceTypeId()都返回TypeId类型,那么我们有必要详细了解以下TypeId这个类。我们从TypeId的源代码中选择了部分公有方法,去除了和属性框架以及追踪框架相关的部分方法。源代码如下:

type-id.cc
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
……
class TypeId
{
public:
/////////////构造函数和操作符重载//////////////
explicit TypeId (const char * name);
inline TypeId ();
inline TypeId (const TypeId &o);
inline TypeId &operator = (const TypeId &o);
inline ~TypeId ();

////////////查找TypeId,和相关信息//////////////
static TypeId LookupByName (std::string name);
static bool LookupByNameFailSafe (std::string name, TypeId *tid);
static TypeId LookupByHash (hash_t hash);
static bool LookupByHashFailSafe (hash_t hash, TypeId *tid);
static uint32_t GetRegisteredN (void);
static TypeId GetRegistered (uint32_t i);

////////////通过TypeId获取或者设置类的信息/////////////////
std::string GetGroupName (void) const;
std::string GetName (void) const;
hash_t GetHash (void) const;
std::size_t GetSize (void) const;
Callback<ObjectBase *> GetConstructor (void) const;
bool HasConstructor (void) const;
TypeId SetParent (TypeId tid);
template <typename T>
TypeId SetParent (void);
TypeId SetGroupName (std::string groupName);
TypeId SetSize (std::size_t size);
template <typename T>
TypeId AddConstructor (void);
uint16_t GetUid (void) const;
void SetUid (uint16_t uid);

////////////通过TypeId了解继承层次关系///////////////
TypeId GetParent (void) const;
bool HasParent (void) const;
bool IsChildOf (TypeId other) const;

/////////////和属性框架相关的方法//////////////////
……略

////////////和追踪系统相关的方法//////////////////
……略
};

这里,我把TypeId的核心方法分成了4类:

  • 构造函数和操作符重载:这类方法定义了我们如何创建和比较一个TypeId
  • 查找TypeId和相关信息:这类方法都是静态方法,定义了我们如何能够获得一个已经存在的TypeId,以及获得全局TypeId的相关信息
  • 通过TypeId设置和获取类的信息:这类方法定义了我们如何通过TypeId的实例去设置一些和其所描述的类相关的信息
  • 通过TypeId解析类相关的继承层次关系:这类方法使得我们可以了解该TypeId的实例所描述的类的继承层次关系

4. 对象框架

4.1. 对象模板代码

要创建一个支持NS-3对象框架的类,需要让对象至少继承自ObjectBase,这个类至少应该重写GetTypeId()和GetInstanceTypeId()两个方法。例如,如果我们需要创建一个名字为MyObject的对象,那么我们需要至少实现如下代码:

test-my-object.cc
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */

#include "ns3/core-module.h"

using namespace ns3;

namespace ns3 { //一般建议将所有对象都写在ns3名称空间中

class MyObject : public ObjectBase //继承自ObjectBase
{
public:
static TypeId GetTypeId (); //必须实现此方法

MyObject ();
virtual ~MyObject();

virtual TypeId GetInstanceTypeId () const; //必须实现此方法
};

NS_LOG_COMPONENT_DEFINE ("MyObject"); //定义一个日志组件
NS_OBJECT_ENSURE_REGISTERED (MyObject); //不要忘记调用

TypeId
MyObject::GetTypeId ()
{
static TypeId tid = TypeId("ns3::MyObject"); //创建TypeId,
tid.SetParent(ObjectBase::GetTypeId());
tid.SetGroupName("MyExample");
tid.AddConstructor<MyObject>(); //这种写法并不推荐
return tid;
}

MyObject::MyObject ()
{
NS_LOG_FUNCTION(this);
}

MyObject::~MyObject ()
{
NS_LOG_FUNCTION(this);
}

TypeId
MyObject::GetInstanceTypeId () const
{
return MyObject::GetTypeId();
}
}

以上就是一个基本的类在NS-3中的实现。其中有几点需要注意:

  • 类最好写在ns3名称空间中,当然要用其他名称空间也是可以的,但是写在ns3空间中,无论使用继承还是调用其他类都会比较方便
  • 使用NS-3的对象框架,类至少要继承自ObjectBase
  • 提供GetTypeId()方法的实现,其中创建一个静态的TypeId对象,然后需要设置其属性:
    • Name:作为构造函数的参数传入,表示类的名字,包含名称空间注意:无论是类名还是名称空间的名字,都无需和实际对象一致,只要提供唯一的字符串即可,但一般为了避免产生歧义和误导用户,我们一般选择让类名的字符串和实际类的名字和所处的名称空间一致
    • Parent:通过SetParent()方法设置类是从哪个类继承的,传入父类的TypeId值,这方便我们仅仅通过TypeId就可获得类的继承关系,方便以后程序逻辑的书写
    • GroupName:设置一个类所属的组别,可以不设置,无实际意义,仅仅是为了区分不同的对象类别
    • Constructor:为类型添加一个构造方法,通过AddConstructor()方法进行设置,无参数,使用模板类型进行标示。注意:如果类型是一个抽象类,则无法添加构造方法
  • GetTypeId()必须是静态方法,此外,其中必须创建并返回一个TypeId对象,
    • 可以使用C++的static关键字修饰该变量,以避免每次调用GetTypeId()方法时重复创建该对象
    • 推荐使用下面这种更加安全的创建方法:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      ……
      TypeId
      MyObject::GetTypeId ()
      {
      static TypeId tid = TypeId("ns3::MyObject")
      .SetParent(ObjectBase::GetTypeId())
      .SetGroupName("MyExample")
      .AddConstructor<MyObject>();
      return tid;
      }
      ……
      这种方法在创建tid的时候就设置和添加了TypeId的各种属性。否则可能出现在反复调用GetTypeId()方法时,重复添加构造函数的错误。
  • 实现GetInstanceTypeId()方法,在没有特殊情况的时候,此方法一般返回类的TypeId,该方法必须有const后缀修饰
  • 最佳实践推荐使用NS_LOG_COMPONENT_DEFINE宏定义一个日志组件,组件的名字推荐和类名一致
  • 必须调用NS_OBJECT_ENSURE_REGISTERED宏来在NS-3对象框架中注册新创建的类,否则无法实现NS-3对象框架的特性
  • 如果类分为头文件和源文件,则上面两个宏需要在源文件中调用,并且最好是写在名称空间中

实现了以上几点之后,一个类就加入了NS-3的对象框架,具有了NS-3类最基本的特性。

4.2. 创建对象

加入了NS-3对象框架之后,就可以通过多种方法来创建对象。最常见的方法就是通过类的名字来创建,如下的代码所示:

try-my-object.cc
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */

#include "ns3/core-module.h"

using namespace ns3;

namespace ns3 { //一般建议将所有对象都写在ns3名称空间中

class MyObject : public ObjectBase //继承自ObjectBase
{
public:
static TypeId GetTypeId (); //必须实现此方法

MyObject ();
virtual ~MyObject();

virtual TypeId GetInstanceTypeId () const; //必须实现此方法

//业务方法
void MyMethod();
};

NS_LOG_COMPONENT_DEFINE ("MyObject"); //定义一个日志组件
NS_OBJECT_ENSURE_REGISTERED (MyObject); //不要忘记调用

TypeId
MyObject::GetTypeId ()
{
static TypeId tid = TypeId("ns3::MyObject"); //创建TypeId,
tid.SetParent(ObjectBase::GetTypeId());
tid.SetGroupName("MyExample");
tid.AddConstructor<MyObject>();
return tid;
}

MyObject::MyObject ()
{
NS_LOG_FUNCTION(this);
}

MyObject::~MyObject ()
{
NS_LOG_FUNCTION(this);
}

TypeId
MyObject::GetInstanceTypeId () const
{
return MyObject::GetTypeId();
}

void
MyObject::MyMethod ()
{
NS_LOG_UNCOND("my method is executed");
}

}

int
main (int argc, char *argv[])
{
LogComponentEnable("MyObject", LOG_LEVEL_LOGIC);

TypeId tid = TypeId::LookupByName("ns3::MyObject"); //通过字符串查找TypeId
Callback<ObjectBase *> constructor = tid.GetConstructor(); //找到其构造函数
MyObject * obj = dynamic_cast<MyObject *>(constructor()); //调用构造函数
obj->MyMethod(); //调用其方法
delete obj; //删除创建的对象
obj = 0;
}

代码中,使用类的名字的字符串查找到了类所对应的TypeId,然后使用TypeId的方法GetConstructor()获取了该类的构造函数的回调(callback,类似于函数指针,将在后面章节详细介绍)。随后,我们调用了该构造函数,并创建了类的一个实例。由于回调函数只能返回基类类型,但我们明确创建的是子类类型,因此这里使用C++的dynmaic_cast方法将指针转换成子类类型。因为使用子类的指针才能正确调用子类的方法。需要注意的是,由于程序中是直接创建了对象,并获取了对象的指针,而未使用智能指针,因此,必须在适当的时候自己删除所创建的对象。当然C++的最佳实践还指出,我们删除了对象之后,还应该将指向该对象的指针赋值为空,这样在以后的程序中才能正确通过判断来确定指针是否为空。

运行程序得到如下输出:

1
2
3
4
5
6
7
8
9
Waf: Entering directory `/home/rainsia/Applications/ns-allinone-3.27/ns-3.27/build'
[ 931/1953] Compiling scratch/try-my-object.cc
[1898/1953] Linking build/scratch/try-my-object
Waf: Leaving directory `/home/rainsia/Applications/ns-allinone-3.27/ns-3.27/build'
Build commands will be stored in build/compile_commands.json
'build' finished successfully (3.445s)
MyObject:MyObject(0x1b73150)
my method is executed
MyObject:~MyObject(0x1b73150)

可见,对象能够通过TypeId被成功创建、使用和销毁。

4.3. 反射机制

由于现在可以从字符串动态地创建对象,因此,我们甚至可以模仿Java中Class.forName()类似的反射机制,从配置文件中读取类的配置信息,动态创建出具体对象。看下面的例子:

try-my-object.cc
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */

#include <iostream>
#include <fstream>
#include <string>

#include "ns3/core-module.h"

using namespace std;
using namespace ns3;

namespace ns3 { //一般建议将所有对象都写在ns3名称空间中

class MyObject : public ObjectBase //继承自ObjectBase
{
public:
static TypeId GetTypeId (); //必须实现此方法

MyObject ();
virtual ~MyObject();

virtual TypeId GetInstanceTypeId () const; //必须实现此方法

//业务方法
virtual void MyMethod();
};

NS_LOG_COMPONENT_DEFINE ("MyObject"); //定义一个日志组件
NS_OBJECT_ENSURE_REGISTERED (MyObject); //不要忘记调用

TypeId
MyObject::GetTypeId ()
{
static TypeId tid = TypeId("ns3::MyObject") //创建TypeId,
.SetParent(ObjectBase::GetTypeId())
.SetGroupName("MyExample")
.AddConstructor<MyObject>();
return tid;
}

MyObject::MyObject ()
{
NS_LOG_FUNCTION(this);
}

MyObject::~MyObject ()
{
NS_LOG_FUNCTION(this);
}

TypeId
MyObject::GetInstanceTypeId () const
{
return MyObject::GetTypeId();
}

void
MyObject::MyMethod ()
{
NS_LOG_FUNCTION(this);

NS_LOG_UNCOND("my method is executed");
}

class MyObject2 : public MyObject {
public:
static TypeId GetTypeId ();

virtual void MyMethod();
};

NS_OBJECT_ENSURE_REGISTERED (MyObject2);

TypeId
MyObject2::GetTypeId ()
{
static TypeId tid = TypeId("ns3::MyObject2")
.SetParent(MyObject::GetTypeId())
.SetGroupName("MyExample")
.AddConstructor<MyObject2>();
return tid;
}

void
MyObject2::MyMethod ()
{
NS_LOG_FUNCTION(this);

NS_LOG_UNCOND("in MyObject2");
}

class MyObject3 : public MyObject {
public:
static TypeId GetTypeId ();

virtual void MyMethod();
};

NS_OBJECT_ENSURE_REGISTERED (MyObject3);

TypeId
MyObject3::GetTypeId ()
{
static TypeId tid = TypeId("ns3::MyObject3")
.SetParent(MyObject::GetTypeId())
.SetGroupName("MyExample")
.AddConstructor<MyObject3>();
return tid;
}

void
MyObject3::MyMethod ()
{
NS_LOG_FUNCTION(this);

NS_LOG_UNCOND("in MyObject3");
}
}

int
main (int argc, char *argv[])
{
LogComponentEnable("MyObject", LOG_LEVEL_LOGIC);

ifstream infile("MyObjectConfig.ini");
std::string line;
getline(infile, line);
if(!line.empty()) {
NS_LOG_INFO("config from file is " << line);
TypeId tid = TypeId::LookupByName(line);
MyObject * obj = dynamic_cast<MyObject *>(tid.GetConstructor()());
obj->MyMethod();
delete obj;
obj = 0;
}
}

我们创建了两个对象MyObject2和MyObject3都继承自MyObject,且分别重写了MyObject的虚方法MyMethod()。然后我们在ns-3的主目录中创建一个名为MyObjectConfig.ini的文本文件,内容如下:

MyObjectConfig.ini
1
ns3::MyObject3

然后我们运行该程序得到如下输出:

1
2
3
4
5
6
7
8
9
10
11
Waf: Entering directory `/home/rainsia/Applications/ns-allinone-3.27/ns-3.27/build'
[1814/1953] Compiling scratch/try-my-object.cc
[1923/1953] Linking build/scratch/try-my-object
Waf: Leaving directory `/home/rainsia/Applications/ns-allinone-3.27/ns-3.27/build'
Build commands will be stored in build/compile_commands.json
'build' finished successfully (2.590s)
config from file is ns3::MyObject3
MyObject:MyObject(0x1ec29b0)
MyObject:MyMethod(0x1ec29b0)
in MyObject3
MyObject:~MyObject(0x1ec29b0)

换句话说,我们通过从文件读取出来的字符串创建了MyObject3对象,并成功调用了其方法。如果我们将文件中的字符串改为:

MyObjectConfig.ini
1
ns3::MyObject2

那么程序运行结果将变为:

1
2
3
4
5
6
7
8
9
Waf: Entering directory `/home/rainsia/Applications/ns-allinone-3.27/ns-3.27/build'
Waf: Leaving directory `/home/rainsia/Applications/ns-allinone-3.27/ns-3.27/build'
Build commands will be stored in build/compile_commands.json
'build' finished successfully (1.114s)
config from file is ns3::MyObject2
MyObject:MyObject(0x210e9b0)
MyObject:MyMethod(0x210e9b0)
in MyObject2
MyObject:~MyObject(0x210e9b0)

值得注意的是,此处,并没有重新编译程序。因为仅仅有配置的文件的改变,而没有程序的改变,但是我们看到的实际结果却是创建出来的对象变成了MyObject2,并且成功调用了其方法。这以前是必须使用Java的反射机制才能实现的功能,现在使用NS-3的对象框架也可以实现。通过这种方法,可以让我们在不用重新编译程序的情况下,动态地决定创建的对象实例究竟是属于哪个类型。

4.4. 和SimpleRefCount结合

上面的代码中使用的依然是普通指针,如果需要使用智能指针来引用对象,最好的方式就是像上文一样让对象继承SimpleRefCount。如果参考上一章中提到的SimpleRefCount的代码可以发现,SimpleRefCount可以通过模板的形式指定一个父类(在上一章的例子当中我们一直未指定该父类,因此父类默认值为空):

simple-ref-count.cc
1
2
3
4
5
template <typename T, typename PARENT = empty, typename DELETER = DefaultDeleter<T> >
class SimpleRefCount : public PARENT
{
……
};

实际上,将SimpleRefCount的父类指定为ObjectBase的话,便可同时拥有SimpleRefCount支持智能指针的特点和ObjectBase支持TypeId的特点。

例如上面的MyObject类,即可改为:

try-my-object.cc
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */

#include <iostream>
#include <fstream>
#include <string>

#include "ns3/core-module.h"

using namespace std;
using namespace ns3;

namespace ns3 { //一般建议将所有对象都写在ns3名称空间中

class MyObject : public SimpleRefCount<MyObject, ObjectBase> //继承自SimpleRefCount,又继承自ObjectBase
{
public:
static TypeId GetTypeId (); //必须实现此方法

MyObject ();
virtual ~MyObject();

virtual TypeId GetInstanceTypeId () const; //必须实现此方法

//业务方法
virtual void MyMethod();
};

NS_LOG_COMPONENT_DEFINE ("MyObject"); //定义一个日志组件
NS_OBJECT_ENSURE_REGISTERED (MyObject); //不要忘记调用

TypeId
MyObject::GetTypeId ()
{
static TypeId tid = TypeId("ns3::MyObject") //创建TypeId,
.SetParent(ObjectBase::GetTypeId())
.SetGroupName("MyExample")
.AddConstructor<MyObject>();
return tid;
}

MyObject::MyObject ()
{
NS_LOG_FUNCTION(this);
}

MyObject::~MyObject ()
{
NS_LOG_FUNCTION(this);
}

TypeId
MyObject::GetInstanceTypeId () const
{
return MyObject::GetTypeId();
}

void
MyObject::MyMethod ()
{
NS_LOG_FUNCTION(this);

NS_LOG_UNCOND("my method is executed");
}

class MyObject2 : public MyObject {
public:
static TypeId GetTypeId ();

virtual void MyMethod();
};

NS_OBJECT_ENSURE_REGISTERED (MyObject2);

TypeId
MyObject2::GetTypeId ()
{
static TypeId tid = TypeId("ns3::MyObject2")
.SetParent(MyObject::GetTypeId())
.SetGroupName("MyExample")
.AddConstructor<MyObject2>();
return tid;
}

void
MyObject2::MyMethod ()
{
NS_LOG_FUNCTION(this);

NS_LOG_UNCOND("in MyObject2");
}

class MyObject3 : public MyObject {
public:
static TypeId GetTypeId ();

virtual void MyMethod();
};

NS_OBJECT_ENSURE_REGISTERED (MyObject3);

TypeId
MyObject3::GetTypeId ()
{
static TypeId tid = TypeId("ns3::MyObject3")
.SetParent(MyObject::GetTypeId())
.SetGroupName("MyExample")
.AddConstructor<MyObject3>();
return tid;
}

void
MyObject3::MyMethod ()
{
NS_LOG_FUNCTION(this);

NS_LOG_UNCOND("in MyObject3");
}
}

int
main (int argc, char *argv[])
{
LogComponentEnable("MyObject", LOG_LEVEL_LOGIC);

ifstream infile("MyObjectConfig.ini");
std::string line;
getline(infile, line);
if(!line.empty()) {
NS_LOG_INFO("config from file is " << line);
TypeId tid = TypeId::LookupByName(line);
Ptr<MyObject> obj = Ptr<MyObject>((MyObject *)tid.GetConstructor()(), false);
obj->MyMethod();
}
}

和之前的MyObject类相比,现在的MyObject类现在继承了SimpleRefCount,因此具备了使用智能指针的能力。同时,我们又通过模板,让SimpleRefCount继承了ObjectBase,因此,类又可以使用TypeId来创建对象,具有了反射的能力。因此在创建对象的时候,可以直接将普通指针转换成智能指针:

1
2
3
……
Ptr<MyObject> obj = Ptr<MyObject>((MyObject *)tid.GetConstructor()(), false);
……

当然,如果每次都这么创建对象,那么语法方面是相当复杂的,并且很容易出错。得益于C++强大(?)的模板支持,我们可以用模板写一个通用方法:

1
2
3
4
template<typename T>
Ptr<T> CreatePtrObject() {
return Ptr<T>(dynamic_cast<T *>(T::GetTypeId().GetConstructor()()), false);
}

可以看出,这个方法和上一个代码片段执行的功能是一样的,只是类型变成了更通用的T模板。以后可以通过直接调用这个方法来方便地创建任何对象的智能指针(当然,前提是类必须支持智能指针):

1
Ptr<MyObject> obj = CreatePtrObject<MyObject>();

这种写法比上面的写法要简单很多,并且不容易出错。对象被成功创建以后,在程序中就无需再显式地销毁对象,而智能指针会管理对象的引用,在引用减少到零的时候,对象自动会被销毁。运行程序后,得到如下输出:

1
2
3
4
5
6
7
8
9
10
11
Waf: Entering directory `/home/rainsia/Applications/ns-allinone-3.27/ns-3.27/build'
[1814/1953] Compiling scratch/try-my-object.cc
[1914/1953] Linking build/scratch/try-my-object
Waf: Leaving directory `/home/rainsia/Applications/ns-allinone-3.27/ns-3.27/build'
Build commands will be stored in build/compile_commands.json
'build' finished successfully (7.773s)
config from file is ns3::MyObject2
MyObject:MyObject(0x18939b0)
MyObject:MyMethod(0x18939b0)
in MyObject2
MyObject:~MyObject(0x18939b0)

可以看出,对象被正常创建,并且可以通过智能指针正确地调用,而指针销毁的时候,对象被同步销毁。

4.5. 使用TypeId判断对象的类型

通过TypeId可以判断真实的对象和类的从属关系,然后通过智能指针的动态转换转换智能指针的类型。例如在如下的代码中:

try-inheritance.cc
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */

#include <iostream>
#include <fstream>
#include <string>

#include "ns3/core-module.h"

using namespace std;
using namespace ns3;

namespace ns3 { //一般建议将所有对象都写在ns3名称空间中

class MyObject : public SimpleRefCount<MyObject, ObjectBase> //继承自SimpleRefCount,又继承自ObjectBase
{
public:
static TypeId GetTypeId (); //必须实现此方法

MyObject ();
virtual ~MyObject();

virtual TypeId GetInstanceTypeId () const; //必须实现此方法

//业务方法
virtual void MyMethod();
};

NS_LOG_COMPONENT_DEFINE ("MyObject"); //定义一个日志组件
NS_OBJECT_ENSURE_REGISTERED (MyObject); //不要忘记调用

TypeId
MyObject::GetTypeId ()
{
static TypeId tid = TypeId("ns3::MyObject") //创建TypeId,
.SetParent(ObjectBase::GetTypeId())
.SetGroupName("MyExample")
.AddConstructor<MyObject>();
return tid;
}

MyObject::MyObject ()
{
NS_LOG_FUNCTION(this);
}

MyObject::~MyObject ()
{
NS_LOG_FUNCTION(this);
}

TypeId
MyObject::GetInstanceTypeId () const
{
return GetTypeId();
}

void
MyObject::MyMethod ()
{
NS_LOG_FUNCTION(this);

NS_LOG_UNCOND("my method is executed");
}

class MyObject2 : public MyObject {
public:
static TypeId GetTypeId ();

virtual void MyMethod();

void MyMethod2();

virtual TypeId GetInstanceTypeId () const; //子类实现此方法才能实现正确识别具体类型
};

NS_OBJECT_ENSURE_REGISTERED (MyObject2);

TypeId
MyObject2::GetTypeId ()
{
static TypeId tid = TypeId("ns3::MyObject2")
.SetParent(MyObject::GetTypeId())
.SetGroupName("MyExample")
.AddConstructor<MyObject2>();
return tid;
}

void
MyObject2::MyMethod ()
{
NS_LOG_FUNCTION(this);

NS_LOG_UNCOND("in MyObject2");
}

TypeId
MyObject2::GetInstanceTypeId () const
{
return GetTypeId();
}

void
MyObject2::MyMethod2 ()
{
NS_LOG_FUNCTION(this);

NS_LOG_UNCOND("method 2 in MyObject2");
}

class MyObject3 : public MyObject {
public:
static TypeId GetTypeId ();

virtual void MyMethod();

void MyMethod3();

virtual TypeId GetInstanceTypeId () const; //子类实现此方法才能实现正确识别具体类型
};

NS_OBJECT_ENSURE_REGISTERED (MyObject3);

TypeId
MyObject3::GetTypeId ()
{
static TypeId tid = TypeId("ns3::MyObject3")
.SetParent(MyObject::GetTypeId())
.SetGroupName("MyExample")
.AddConstructor<MyObject3>();
return tid;
}

void
MyObject3::MyMethod ()
{
NS_LOG_FUNCTION(this);

NS_LOG_UNCOND("in MyObject3");
}

void
MyObject3::MyMethod3 ()
{
NS_LOG_FUNCTION(this);

NS_LOG_UNCOND("method 3 in MyObject3");
}


TypeId
MyObject3::GetInstanceTypeId () const
{
return GetTypeId();
}

}

void TestType(Ptr<MyObject> obj)
{
TypeId tid = obj->GetInstanceTypeId();
NS_LOG_LOGIC("obj type is " << tid.GetName());
if (tid == MyObject2::GetTypeId()) //此处要注意,tid在实现的时候必须是static才能正确比较相等
{
NS_LOG_LOGIC("is instance of MyObject2, call method2");
Ptr<MyObject2> obj2 = DynamicCast<MyObject2, MyObject>(obj);
obj2->MyMethod2();
}
else if(tid == MyObject3::GetTypeId())
{
NS_LOG_LOGIC("is instance of MyObject3, call method3");
Ptr<MyObject3> obj3 = DynamicCast<MyObject3, MyObject>(obj);
obj3->MyMethod3();
}
}

int
main (int argc, char *argv[])
{
LogComponentEnable("MyObject", LOG_LEVEL_LOGIC);

ifstream infile("MyObjectConfig.ini");
std::string line;
getline(infile, line);
if(!line.empty()) {
NS_LOG_INFO("config from file is " << line);
TypeId tid = TypeId::LookupByName(line);
Ptr<MyObject> obj = Ptr<MyObject>((MyObject *)tid.GetConstructor()(), false);
obj->MyMethod();

TestType(obj);
}
}

MyObject2和MyObject3两个类继承了MyObject类,前两者分别有自己特殊的方法MyMethod2()和MyMethod3()。程序中具有一个函数TestType(),该函数接受一个MyObject对象的智能指针,然后判断这个指针所指向的对象到底属于哪个类型,然后才确定调用具体的哪个方法。在实现这个功能的过程中,需要注意以下几点:

  • 必须重写每个类的GetInstanceTypeId()方法(虽然代码都是一样的)
  • 在上一条的基础上,可以使用实例的TypeId的值和类的TypeId的值来对比判断具体的类型归属
  • 在上一条的基础上,可以使用智能指针提供的动态转换方法DynamicCast<T, U>()来进行智能指针的类型转换,例如:
1
2
3
……
Ptr<MyObject3> obj3 = DynamicCast<MyObject3, MyObject>(obj);
……

上面的代码中将MyObject类型的智能指针转换成MyObject3类型的智能指针。当然,前提是这两种指针是能够互相转换成功的,一般这种情况多出现在要将父类的指针转换成子类的指针的时候。

在上面的例子中,一旦指针转换成功后,即可调用不同子类的独有方法。例如MyObject2有自己的独有方法MyMethod2(),而MyObject3有自己的独有方法MyObject3()。

运行上面的程序后得到的运行结果为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Waf: Entering directory `/home/rainsia/Applications/ns-allinone-3.27/ns-3.27/build'
[1815/1955] Compiling scratch/try-inheritance.cc
[1944/1955] Linking build/scratch/try-inheritance
Waf: Leaving directory `/home/rainsia/Applications/ns-allinone-3.27/ns-3.27/build'
Build commands will be stored in build/compile_commands.json
'build' finished successfully (2.455s)
config from file is ns3::MyObject2
MyObject:MyObject(0x16489b0)
MyObject:MyMethod(0x16489b0)
in MyObject2
obj type is ns3::MyObject2
is instance of MyObject2, call method2
MyObject:MyMethod2(0x16489b0)
method 2 in MyObject2
MyObject:~MyObject(0x16489b0)

从运行结果可以看出,TestType方法中,我们可以通过TypeId区分到底是传入的是MyObject2对象还是MyObject3对象,并正确调用其独有方法。

4.6. 使用TypeId判断类的继承关系

TypeId除了可以判断对象和类的归属之外,还可以判断类之间的继承关系,或者获取父子类的信息。例如,我们在上面程序的基础上进行修改,再添加两个类MyObject4和MyObject5分别继承自MyObject2和MyObject3。我们修改程序的TestType方法,只接受MyObject2及其子类,才能调用方法,而MyObject3及其子类,将提示错误。

try-inheritance.cc
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */

#include <iostream>
#include <fstream>
#include <string>

#include "ns3/core-module.h"

using namespace std;
using namespace ns3;

namespace ns3 { //一般建议将所有对象都写在ns3名称空间中

class MyObject : public SimpleRefCount<MyObject, ObjectBase> //继承自SimpleRefCount,又继承自ObjectBase
{
public:
static TypeId GetTypeId (); //必须实现此方法

MyObject ();
virtual ~MyObject();

virtual TypeId GetInstanceTypeId () const; //必须实现此方法

//业务方法
virtual void MyMethod();
};

NS_LOG_COMPONENT_DEFINE ("MyObject"); //定义一个日志组件
NS_OBJECT_ENSURE_REGISTERED (MyObject); //不要忘记调用

TypeId
MyObject::GetTypeId ()
{
static TypeId tid = TypeId("ns3::MyObject") //创建TypeId,
.SetParent(ObjectBase::GetTypeId())
.SetGroupName("MyExample")
.AddConstructor<MyObject>();
return tid;
}

MyObject::MyObject ()
{
NS_LOG_FUNCTION(this);
}

MyObject::~MyObject ()
{
NS_LOG_FUNCTION(this);
}

TypeId
MyObject::GetInstanceTypeId () const
{
return GetTypeId();
}

void
MyObject::MyMethod ()
{
NS_LOG_FUNCTION(this);

NS_LOG_UNCOND("my method is executed");
}

class MyObject2 : public MyObject {
public:
static TypeId GetTypeId ();

virtual void MyMethod();

void MyMethod2();

virtual TypeId GetInstanceTypeId () const; //子类实现此方法才能实现正确识别具体类型
};

NS_OBJECT_ENSURE_REGISTERED (MyObject2);

TypeId
MyObject2::GetTypeId ()
{
static TypeId tid = TypeId("ns3::MyObject2")
.SetParent(MyObject::GetTypeId())
.SetGroupName("MyExample")
.AddConstructor<MyObject2>();
return tid;
}

void
MyObject2::MyMethod ()
{
NS_LOG_FUNCTION(this);

NS_LOG_UNCOND("in MyObject2");
}

TypeId
MyObject2::GetInstanceTypeId () const
{
return GetTypeId();
}

void
MyObject2::MyMethod2 ()
{
NS_LOG_FUNCTION(this);

NS_LOG_UNCOND("method 2 in MyObject2");
}

class MyObject3 : public MyObject {
public:
static TypeId GetTypeId ();

virtual void MyMethod();

void MyMethod3();

virtual TypeId GetInstanceTypeId () const; //子类实现此方法才能实现正确识别具体类型
};

NS_OBJECT_ENSURE_REGISTERED (MyObject3);

TypeId
MyObject3::GetTypeId ()
{
static TypeId tid = TypeId("ns3::MyObject3")
.SetParent(MyObject::GetTypeId())
.SetGroupName("MyExample")
.AddConstructor<MyObject3>();
return tid;
}

void
MyObject3::MyMethod ()
{
NS_LOG_FUNCTION(this);

NS_LOG_UNCOND("in MyObject3");
}

void
MyObject3::MyMethod3 ()
{
NS_LOG_FUNCTION(this);

NS_LOG_UNCOND("method 3 in MyObject3");
}


TypeId
MyObject3::GetInstanceTypeId () const
{
return GetTypeId();
}

class MyObject4 : public MyObject2 {
public:
static TypeId GetTypeId ();

virtual void MyMethod();

virtual TypeId GetInstanceTypeId () const; //子类实现此方法才能实现正确识别具体类型
};

NS_OBJECT_ENSURE_REGISTERED (MyObject4);

TypeId
MyObject4::GetTypeId ()
{
static TypeId tid = TypeId("ns3::MyObject4")
.SetParent(MyObject2::GetTypeId())
.SetGroupName("MyExample")
.AddConstructor<MyObject4>();
return tid;
}

void
MyObject4::MyMethod ()
{
NS_LOG_FUNCTION(this);

NS_LOG_UNCOND("in MyObject4");
}

TypeId
MyObject4::GetInstanceTypeId () const
{
return GetTypeId();
}

class MyObject5 : public MyObject3 {
public:
static TypeId GetTypeId ();

virtual void MyMethod();

virtual TypeId GetInstanceTypeId () const; //子类实现此方法才能实现正确识别具体类型
};

NS_OBJECT_ENSURE_REGISTERED (MyObject5);

TypeId
MyObject5::GetTypeId ()
{
static TypeId tid = TypeId("ns3::MyObject5")
.SetParent(MyObject3::GetTypeId())
.SetGroupName("MyExample")
.AddConstructor<MyObject5>();
return tid;
}

void
MyObject5::MyMethod ()
{
NS_LOG_FUNCTION(this);

NS_LOG_UNCOND("in MyObject5");
}

TypeId
MyObject5::GetInstanceTypeId () const
{
return GetTypeId();
}
}

void TestType(Ptr<MyObject> obj)
{
TypeId tid = obj->GetInstanceTypeId();
TypeId tid2 = MyObject2::GetTypeId();
NS_LOG_LOGIC("obj type is " << tid.GetName());
if(tid == tid2 || tid.IsChildOf(tid2)) {
NS_LOG_LOGIC("这是正确的对象类型");
} else {
NS_LOG_LOGIC("对象类型不能被接受,父类是" << tid.GetParent().GetName());
}
}

int
main (int argc, char *argv[])
{
LogComponentEnable("MyObject", LOG_LEVEL_LOGIC);

TypeId tid = MyObject2::GetTypeId();
Ptr<MyObject> obj = Ptr<MyObject>((MyObject *)tid.GetConstructor()(), false);

TestType(obj);
}

从TestType()函数中可以看出,如何判断类的继承关系。其中if语句有两个条件,第一个条件表示传入的对象类型和MyObject2的类型一致;第二个条件表示传入的对象类型是MyObject2类的子类。当满足这两个条件时,我们认为传入的对象类型可接受。

该程序运行的结果如下:

1
2
3
4
5
6
7
8
9
10
Waf: Entering directory `/home/rainsia/Applications/ns-allinone-3.27/ns-3.27/build'
[1817/1955] Compiling scratch/try-inheritance.cc
[1906/1955] Linking build/scratch/try-inheritance
Waf: Leaving directory `/home/rainsia/Applications/ns-allinone-3.27/ns-3.27/build'
Build commands will be stored in build/compile_commands.json
'build' finished successfully (2.492s)
MyObject:MyObject(0x1b689b0)
obj type is ns3::MyObject2
这是正确的对象类型
MyObject:~MyObject(0x1b689b0)

当传入的类型不满足条件的时候,我们认为传入的对象不可接受,然后输出错误信息,同时,程序通过TypeId的GetParent()方法可以轻易地获得该类的父类信息。这里,我们简单地获取了其父类的名字。我们将创建的对象的类型改为MyObject5,然后再次运行程序,可以得到如下输出:

1
2
3
4
5
6
7
8
9
10
Waf: Entering directory `/home/rainsia/Applications/ns-allinone-3.27/ns-3.27/build'
[1818/1955] Compiling scratch/try-inheritance.cc
[1944/1955] Linking build/scratch/try-inheritance
Waf: Leaving directory `/home/rainsia/Applications/ns-allinone-3.27/ns-3.27/build'
Build commands will be stored in build/compile_commands.json
'build' finished successfully (2.458s)
MyObject:MyObject(0x133b9b0)
obj type is ns3::MyObject5
对象类型不能被接受,父类是ns3::MyObject3
MyObject:~MyObject(0x133b9b0)

可见,从TypeId可以正确地获取父类的信息。

本文标题:NS-3学习笔记(四):NS-3的对象框架 之 TypeId

文章作者:Rain Sia

发布时间:2018年04月08日 - 14:04

最后更新:2018年10月29日 - 10:10

原始链接: http://rainsia.github.io/2018/04/08/ns3-004/

版权信息:本文为作者原创文章,如需进行非商业性转载,请注明出处并保留原文链接及作者。如要进行商业性转载,请获得作者授权!

联系方式:rainsia@163.com