title: 03,NS-3 的对象框架 之 智能指针
tags: 新建, 模板, 小书匠
- slug: storywriter/upgrade_log
- grammar_mindmap: true
- renderNumberedHeading: true
- grammar_code: true
- grammar_decorate: true
- grammar_mathjax: true
一, NS-3 的对象框架之智能指针
NS-3 提供了一套基于引用计数的智能指针系统, 可以使得对象在不再被使用时自动被删除. 使用一个 Ref()方法在有新的指针指向对象时, 将其内部的计数加 1; 而在指针不再指向时调用 UnRef()方法, 将计数器减 1. 当计数器为 0 的时候, 说明没有任何指针再指向该对象, 那么说明对象不再被需要, 从而可以直接删除. 为了实现这种自动计数的智能指针机制, NS-3 使用了两个类: SimpleRefCount 和 Ptr.
1. NS-3 对象框架概述
1.1 三个特殊基类
SimpleRefCount: 继承该类将得到智能指针特性, 经典类 Packet(网络中的一个数据包)
ObjectBase: 继承该类将得到 NS-3 的属性系统支持, 经典类 Tag(包中标记)
Object: 继承自该对象相当于继承了上面两个对象, 此外还支持聚合特性支持
2. SimpleRefCount 和智能指针 Ptr
2.1 Ptr
** 产生新的引用加 1 的五种情况:**
Ptr (): 默认无参构造函数, 如果构造一个空的 Ptr 对象, 那么其指向的对象为空.
Ptr (T *ptr): 如果构造函数传入的参数是一个指针, 那么将引用加 1.
Ptr (Ptr const&o): 拷贝构造函数, 如果将一个 Ptr 对象复制了一次, 那么引用加 1.
Ptr (Ptr const &o): 带类型转换的拷贝构造函数, 如果将一个其他类型的 Ptr 对象复制了一次, 那么引用也加 1.
operator = (Ptr const& o): 赋值的时候, 相当于改变了引用的指向, 原来引用的对象的计数要减 1, 新引用的对象的计数要加 1.
调用了 Unref()方法的函数就是让引用减 1, 具体情况有如下两种
~Ptr (): 析构函数, 当一个引用对象的 Ptr 被销毁的时候, 说明指向少了一个
operator = (Ptr const& o): 使用赋值的时候, 如果不是将自己赋值给自己, 那么就有可能产生引用的增减. 原来引用的对象的的引用要减 1, 新引用的对象的计数要加 1.
** Ptr 类有一个特殊的构造函数, 可以让用户在创建引用的时候, 不产生任何的计数:**
Ptr (T *ptr, bool ref): 当构造函数的第二个参数为 false 时, 将不再产生任何计数. 此时用户可以自己维护对象的删除.
2.1.1 创建智能指针, 使用 Ptr 的时候一般写法是:
- Ptr<SomeObject> obj = Create<SomeObject>(); // 创建智能指针指向的对象, 该方法自动调用构造函数
- obj->SomeMethod(); // 当作普通指针使用
2.1.2 通过外来指针构造智能指针
- SelfRefObject * p_obj = new SelfRefObject();// 创建对象的时候, 其基本计数已经为 1
- Ptr<SelfRefObject> obj = Ptr<SelfRefObject>(p_obj);// 调用 Ptr 构造函数的时候, 其引用计数将加 1, 因此计数为 2
如果对象不是通过 Create()方法创建的, 而是通过构造函数传入的, 那么将不会自动被销毁. 其原因在于, NS-3 认为这个对象开始并没有被智能指针维护, 因此如果 NS-3 销毁了这个对象, 可能会影响其他地方继续使用该对象, 从而引起错误. 因此, 程序员应该自动维护该对象占用的空间.
正确的使用方法, 如果要让 NS-3 帮我们维护智能指针, 我们可以使用另外一个 Ptr 的构造函数, 传入参数 false 让 Ptr 不再增加引用计数
- SelfRefObject * p_obj = new SelfRefObject();
- Ptr<SelfRefObject> obj = Ptr<SelfRefObject>(p_obj, false);
- obj->SomeMethod();
现在能够获得正确计数, 并帮我们销毁了对象
警告: 然而需要注意的是, 如果 NS-3 以智能指针的方式帮我们维护了一个外来的指针, 那么这个智能指针的对象销毁之后, 外来指针指向的对象也将无法继续使用.
2.1.3 智能指针的拷贝构造函数
总体来说, 在 C++ 中, 有三种情况会调用拷贝构造函数:
一个对象以值传递的方式传入函数体
一个对象以值传递的方式从函数返回
一个对象需要通过另外一个对象进行初始化
2.1.4 智能指针的赋值操作副重载
将一个对象赋值给另外一个对象, C++ 中有如下规则:
对象在声明的同时将另一个已存在的对象赋给它, 就会调用拷贝构造函数;//A b (a);
如果对象已经存在了, 然后再将另一个已存在的对象赋给它, 调用的就是重载赋值运算符.//A c = b;c = a;
2.2 SimpleRefCount
NS-3 提供了一个最简单的 SimpleRefCount 类, 该类已经实现 Ref()和 Unref()方法, 并且在内部维护了引用计数, 可以和 Ptr 智能指针完美结合, 比自己实现 Ref 和 Unref 方法更加安全可靠.
二, NS-3 的对象框架之 TypeId
NS-3 提供的这样一套动态创建对象机制 TypeId 类, 可以动态从字符串创建对象, 还可以判断对象的所属的继承属性. TypeId 还是 NS-3 的属性框架 (Attribute Framework) 和追踪框架 (Tracing Framework) 的基础.
1.ObjectBase 类(抽象类)
ObjectBase 是整个 NS-3 对象框架的基础. 为 NS-3 的其他对象功能提供必要的支持. 由于 TypeId 本身是没有任何作用的. 它必须在一个类的内部定义, 而这个类一般都继承自 ObjectBase.
ObjectBase 类其中的共有方法可分为三大功能模块: TypeId 支持, 属性框架支持, 追踪框架支持
1.1 TypeId 支持
GetTypeId(): 通过类获得当前类的 TypeId. 静态方法, 可直接通过类来调用:
ObjectBase::GetTypeId()
GetInstanceTypeId(): 通过类的实例获得其 TypeId. 实例方法, 并且是一个纯虚方法
1.2 属性框架支持
SetAttribute(): 为对象设置一个属性
SetAttributeFailSafe(): 为对象设置一个属性, 出现错误的时候不会停止运行
GetAttribute(): 获取对象的某个属性
GetAttributeFailSafe(): 获取对象的某个属性, 出现错误的时候不会停止程序的运行
1.3 追踪框架支持
TraceConnect(): 链接对象的某个追踪源
TraceConnectWithoutContext(): 连接对象的某个追踪源, 但是不携带上下文信息
TraceDisconnect(): 断开对象的某个追踪源, 不再继续追踪属性的变化和事件的发生
TraceDisconnectWithoutContext(): 断开不带上下文的某个追踪源
2 TypeId
TypeId 类的四类核心方法:
构造函数和操作符重载: 这类方法定义了我们如何创建和比较一个 TypeId
查找 TypeId 和相关信息: 这类方法都是静态方法, 定义了我们如何能够获得一个已经存在的 TypeId, 以及获得全局 TypeId 的相关信息
通过 TypeId 设置和获取类的信息: 这类方法定义了我们如何通过 TypeId 的实例去设置一些和其所描述的类相关的信息
通过 TypeId 解析类相关的继承层次关系: 这类方法使得我们可以了解该 TypeId 的实例所描述的类的继承层次关系
3. 创建对象
- #include<ns3/core-module.h>
- using namespace std;
- using namespace ns3;
- namespace ns3{
- class MyObject:public 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(){
- //1. 创建 TypeId
- static TypeId tid = TypeId("ns3::MyObject")
- .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_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());//dynmaic_cast 方法将指针转换成子类类型
- obj->MyMethod();
- delete obj;// 未使用智能指针, 因此, 必须在适当的时候自己删除所创建的对象
- obj=0;
- }
4. 反射机制
可以模仿 Java 中 Class.forName()类似的反射机制, 从配置文件中读取类的配置信息, 动态创建出具体对象
- 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;
- }
通过从文件读取出来的字符串创建了 MyObject3 对象, 并成功调用了其方法, 可以让我们在不用重新编译程序的情况下, 动态地决定创建的对象实例究竟是属于哪个类型.
5. 和 SimpleRefCount 结合
- class MyObject : public SimpleRefCount<MyObject, ObjectBase> // 继承自 SimpleRefCount, 又继承自 ObjectBase
- {
- public:
- static TypeId GetTypeId (); // 必须实现此方法
- MyObject ();
- virtual ~MyObject();
- virtual TypeId GetInstanceTypeId () const; // 必须实现此方法
- // 业务方法
- virtual void MyMethod();
- };
- //
- TypeId tid = TypeId::LookupByName(line);
- Ptr<MyObject> obj = Ptr<MyObject>((MyObject *)tid.GetConstructor()(), false);
- template<typename T>
- Ptr<T> CreatePtrObject() {
- return Ptr<T>(dynamic_cast<T *>(T::GetTypeId().GetConstructor()()), false);
- }
- Ptr<MyObject> obj = CreatePtrObject<MyObject>();
6. 使用 TypeId 判断对象的类型
- 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);
- }
- }
7. 使用 TypeId 判断类的继承关系
- 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());
- }
- }
三, NS-3 的对象框架之属性框架
使用 NS-3 的属性框架, 可方便地对对象属性进行设置和读取, 可设置类的默认属性. 在 NS-3 中所有内置属性都可和字符串属性类型相互转换. NS-3 的属性框架主要是通过 ObjectBase 和 TypeId 来实现的, 因此要使用属性框架的类, 必须继承自 ObjectBase, 并且维护一个 TypeId 实例.
在 NS-3 中, 我们现在使用其对象和属性框架的步骤一般如下:
使用 CreateObject<>()方法创建某个类的对象实例, 并得到一个指向该对象的智能指针.
使用对象的 SetAttribute()方法来设置对象的各种属性.
调用 Initialize()方法来初始化对象, 因为 SetAttribute()方法设置的属性, 无法在构造函数中初始化.
1. 属性框架简介
type-id.cc AddAttribute()方法的参数的意义
name: 属性的名字
help: 用一句话来解释一下属性的作用
flags: 用于控制属性读写特性的参数
initialValue: 属性的初始值, 类型为 AttributeValue.AttributeValue 是一切属性值类型的父类, 例如整形值 UintegerValue, 浮点数值 DoubleValue 以及字符串值 StringValue 等. 本章后面部分会介绍不同的属性值类型.
accessor: 如何访问该属性, 是通过对象的成员变量访问呢? 还是通过 Getter/Setter 方法来访问? 其类型是一个 AttributeAccessor 的子类.
checker: 检查属性的值是否符合要求
supportLevel: 表示这个属性是处于使用状态, 不推荐状态, 还是过时状态, 其类型是一个枚举变量, 共有三种值:
SUPPORT: 属性正在受到支持, 可以正常使用, 默认值
DEPRECATED: 属性快要过时, 不支持使用
OBSOLETE: 属性已经过时, 不能使用
supportMsg: 支持性字符串, 当 supportLeve 为不同的值时, msg 的值也将不同:
SUPPORT: 无作用, 可以使用默认值空字符串("")
DEPRECATED: 提示属性快要过时, 给出解决方法, 以后该用什么属性替代该属性
OBSOLETE: 提示属性已经过时, 不能使用, 并提示使用什么属性来替代
其中 flags 参数的取值是由 TypeId 中定义的枚举类型描述的:
- enum AttributeFlag {
- ATTR_GET = 1<<0, /**<只读属性 */
- ATTR_SET = 1<<1, /**< 只写属性 */
- ATTR_CONSTRUC = 1<<2, /**< 属性只能在构造时被初始化 */
- ATTR_SGC = ATTR_GET | ATTR_SET | ATTR_CONSTRUCT, /**< 可读可写可初始化 */
- };
在没有 flags 参数的重载中, flags 的值默认为 ATTR_SGC, 即包含全部的特性.
- enum AttributeFlag {
- ATTR_GET = 1<<0, /**< 只读属性 */
- ATTR_SET = 1<<1, /**< 只写属性 */
- ATTR_CONSTRUC = 1<<2, /**< 属性只能在构造时被初始化 */
- ATTR_SGC = ATTR_GET | ATTR_SET | ATTR_CONSTRUCT, /**< 可读可写可初始化 默认 */
- };
1.1. 定义属性
为了更好地使用属性, 框架, NS-3 提供了 Object 类 (继承自 ObjectBase 和 SimpleRefCount),Object 类还实现了 GetInstanceTypeId() 方法, 而子类无需在重写此方法. 然而要注意, 要让 GetInstanceTypeId()自动能够获取任意子类的正确 TypeId 类型, 必须使用 NS-3 提供的 CreateObject()函数来创建对象, 否则 GetInstanceTypeId()方法返回的总是 Object 自己的 TypeId. 而 NS-3 的整个属性框架的基础就是 TypeId, 因此必须保证 TypeId 能够描述正确的类.
- TypeId MyObject::GetTypeId (){
- static TypeId tid = TypeId("ns3::MyObject") // 创建 TypeId,
- .SetParent(Object::GetTypeId())
- .SetGroupName("MyExample")
- .AddConstructor<MyObject>()
- .AddAttribute ("MyValue",
- "An example attribute",
- TypeId::ATTR_SGC,
- UintegerValue (100),
- MakeUintegerAccessor (&MyObject::m_myValue),
- MakeUintegerChecker<uint32_t> ())
- ;
- return tid;
- }
注意: 要想使变量 m_myValue 初始化成功为 100, 有一个必要条件, 那就是属性的 flags 参数必须为 ATTR_CONSTRUCT 或者 ATTR_SGC 二者之一, 换句话说, 必须包含 ATTR_CONSTRUCT.
1.2 设置和访问属性
除了调用 Getter 和 Setter 方法来访问和设置属性之外, ObjectBase 还提供了 GetAttribute 和 SetAttribute 两个方法来完成同样的工作, 即便是在我们自己没有实现 Getter/Setter 方法的情况下, 这两个方法一直都存在.
- Ptr<MyObject> obj = CreateObject<MyObject>();
- obj->MyMethod();
- obj->SetAttribute("MyValue", UintegerValue(200));
- UintegerValue myValue;
- obj->GetAttribute("MyValue", myValue);
- NS_LOG_UNCOND(myValue.Get());
也可以使用 CreateObjectWithAttributes()函数在创建对象的时候就设置属性 (最多 9 个属性), 其语法和 CreateObject() 函数类似, 但是可以添加属性的名称和值作为参数:
- Ptr<MyObject> obj = CreateObjectWithAttributes<MyObject>("MyValue", UintegerValue(200));
- Ptr<Object> obj = CreateObjectWithAttributes("name1", value1, "name2", value2, ..., "name9", value9);
1.3. 改变属性的默认值
ns-3 提供了一种创建一批对象, 然后一一修改它们属性值的方法: 改变对象属性的默认值. 这种方式就是 NS-3 提供的 Config::SetDefault()静态方法. 其语法如下:
- Config::SetDefault("ns3::ClassName::AttributeName", AttributeValue);
- Config::SetDefault("ns3::MyObject::MyValue", UintegerValue(500));
2. 属性详解
2.1 属性值详解
NS-3 提供了很多属性值的类型, 这些类型都有一个共同的父类: AttributeValue.
AttributeValue 的特性:
AttributeValue 可以支持智能指针
AttributeValue 是抽象类, 不能创建实例, 只能创建其具体子类的实例
SerializeToString 和 DeserializeFromString 是两个纯虚方法, 必须由子类实现
所有 AttributeValue 都提供了转变成字符串的能力
(几乎)所有 AttributeValue 都提供了从字符串恢复的能力
2.2 属性访问器
在创建属性的时候, 除了必须明确属性的值类型之外, 还必须绑定一个属性访问器, 用来确定这个最终存储这个属性值的成员变量究竟是谁.
2.3 属性检查器
属性访问器只负责读写属性的值, 不负责检查属性的值是否正确. 而是使用检查器确保来确保设置到属性上的值符合特殊的要求, 检查器最主要的作用就是检查属性是否合法, 以及从字符串恢复一个合法的属性值.
2.4. 原始类型的属性值类型
不同的属性值类型总是继承自 AttributeValue
常见的原始数据类型的属性值类型有:
- BooleanValue
- DoubleValue
- IntegerValue
- UintegerValue
- StringValue
- 2.4.1 BooleanValue
其底层值类型为 bool; 可以在创建 BooleanValue 时通过构造函数参数初始化其初始值; 可以将值转换成字符串, 也可以从字符串获得其值: 可见转换成字符串时, 其值会变成 "true" 或者 "false". 而从字符串转回 BooleanValue 时, 可以识别 "true","1" 和 "t" 为真, 也可以识别 "false","0" 和 "f" 为假, 如果发现其他值将转换失败.
2.4.1.2. BooleanValue 的访问器
由于无需进行特殊的实现, BooleanValue 中的访问器是使用 NS-3 提供的宏 ATTRIBUTE_CHECKER_DEFINE()自动生成的:
这个宏会展开两个函数:
变量访问器: MakeBooleanAccessor(BooleanValue a1): 只有一个参数, 接受类的成员变量的地址
方法访问器: MakeBooleanAccessor(BooleanValue a1, BooleanValue a2): 有两个参数, 分别接受成员变量的 Getter 和 Setter 方法.
- MakeBooleanAccessor(&MyObject::m_myBoolValue);
- MakeBooleanAccessor(&MyObject::GetMyBoolValue, &MyObject::SetMyBoolValue);
2.4.1.3. BooleanValue 的检查器
由于 BooleanValue 无需进行范围等合法性的检查, NS-3 默认使用了宏来生成访问器的标准实现, 首先在头文件里调用了检查器申明宏模板:
ATTRIBUTE_CHECKER_DEFINE (Boolean);
这个宏展开之后, 其实是生成了一个 BooleanChecker 类和一个 MakeBooleanChecker()函数.
调用 MakeBooleanChecker()函数实际上是返回了一个通过 MakeSimpleAttributreChecker()函数生成的类, 这个类实际上实现通过底层的 SimpleAttributeChecker 类实现了 BooleanChecker 类的各种默认方法. 这个默认的 BooleanChecker 类实际上没有做任何实质性的检查, 只是判断了一下类型是否兼容.
2.4.1.4. BooleanValue 的使用
- TypeId
- MyObject::GetTypeId ()
- {
- static TypeId tid = TypeId("ns3::MyObject") // 创建 TypeId,
- .SetParent(Object::GetTypeId())
- .SetGroupName("MyExample")
- .AddConstructor<MyObject>()
- .AddAttribute ("MyValue",
- "An example attribute",
- TypeId::ATTR_SGC,
- UintegerValue (100),
- MakeUintegerAccessor (&MyObject::m_myValue),
- MakeUintegerChecker<uint32_t> ())
- .AddAttribute ("MyBoolValue",
- "An example bool attribute",
- BooleanValue (false),
- MakeBooleanAccessor (&MyObject::IsMyBoolValue, &MyObject::SetMyBoolValue),
- MakeBooleanChecker ())
- ;
- return tid;
- }
- 2.4.2. DoubleValue
2.4.2.1. DoubleValue 的检查器
- // 如果属性的值小于某个值, 则检查不通过, 属性赋值失败
- template <typename T>
- Ptr<const AttributeChecker> MakeDoubleChecker (double min);
- // 如果属性的值不在某个范围内, 则检查不通过, 赋值失败
- template <typename T>
- Ptr<const AttributeChecker> MakeDoubleChecker (double min, double max);
- // 要注意的 DoubleValueChecker 可以同时支持单精度和双精度两种浮点值类型, 区别在于调用检查器创建函数的时候的时候需要通过模板参数指定具体类型
- MakeDoubleChecker<double>();
- MakeDoubleChecker<float>(50.5);
- MakeDoubleChecker<double>(9.9, 99.9);
2.4.2.2. DoubleValue 的使用
- TypeId
- MyObject::GetTypeId ()
- {
- static TypeId tid = TypeId("ns3::MyObject") // 创建 TypeId,
- .SetParent(Object::GetTypeId())
- .SetGroupName("MyExample")
- .AddConstructor<MyObject>()
- .AddAttribute ("MyDoubleValue",
- "An example double attribute",
- DoubleValue (0.0),
- MakeDoubleAccessor (&MyObject::m_myDoubleValue),
- MakeDoubleChecker<double> (5))
- ;// 需要识别类型能不能赋值给 double 类型; 其次, 检查类型是不是大于等于 5.
- return tid;
- }
在 NS-3 当中, 如果 DoubleValue 不指定最大值, 那么检查的最大值将由当前类型所能表达的最大值决定.
2.4.3. IntegerValue 和 UintegerValue
IntegerValue 表示整型属性值类型, 而 UintegerValue 表示无符号整型属性值类型. 他们的用法与 DoubleValue 非常类似: 他们的检查器必须指定类型, 此外, 还可以指定最小值与最大值. 如果最大值未指定, 那么将有类型的最大值来限制.
整型与无符号整型所能表示的最大原始类型为 64 位整型: int64_t 与 uint64_t. 而取值范围小于 64 位的整型类型都能表示, 例如:(u)int8_t,(u)int16_t 和(u)int32_t 等.
2.4.4. StringValue
2.4.4.1. StringValue 的定义
StringValue 用来表示底层类型为 std::string 的属性值类型. 其定义非常简单, 所有类型全是由 NS-3 的宏自动展开的:
2.4.4.2. StringValue 的自动类型转换
NS-3 当中的 StringValue 最大的特色在于,(几乎)任何其他的属性值类型都能使用 StringValue 表示, 并且赋值成功. 其原因在于所有属性值类型的父类 AttributeValue 当中定义了 DeserializeFromString 虚方法. 因此, 任何子类都必须实现此方法, 也就具备了将字符串转换成具体属性值的能力.
- Ptr<MyObject> obj = CreateObject<MyObject>();
- obj->SetAttribute("MyValue", StringValue("1000"));
- obj->SetAttribute("MyBoolValue", StringValue("t"));
- obj->SetAttribute("MyDoubleValue", StringValue("5.5"));
2.4.4.3 手动类型转换
将 StringValue 类型转换成其他属性值类型
- StringValue value("5.5");
- Ptr<DoubleValue> doubleValue = DynamicCast<DoubleValue>(MakeDoubleChecker<double>()->CreateValidValue(value));
- double number = doubleValue->Get();
将任何其他类型转换为 StringValue 值类型
- DoubleValue value(5.5);
- StringValue stringValue(value.SerializeToString(MakeStringChecker()));
- NS_LOG_UNCOND(stringValue.Get());
2.5 枚举类型的属性值类型
有些变量的取值范围不是连续的, 而是离散的, 那么可以使用 C++ 的枚举类型来定义. 在 NS-3 中, 可以将枚举类型定义为属性的值类型 EnumValue.
2.5.1. EnumValue 的访问器
- template <typename T1>
- Ptr<const AttributeAccessor> MakeEnumAccessor (T1 a1){
- return MakeAccessorHelper<EnumValue> (a1);
- }
- template <typename T1, typename T2>
- Ptr<const AttributeAccessor> MakeEnumAccessor (T1 a1, T2 a2){
- return MakeAccessorHelper<EnumValue> (a1, a2);
- }
2.5.2. EnumValue 的检查器
EnumValue 的检查器主要用来检查取值是否在枚举类型列表当中, 在创建检查器的时候需要传入枚举类型值的列表. NS-3 提供了函数来创建 EnumValue 的检查器:
- enum.h
- Ptr<const AttributeChecker> MakeEnumChecker (int v1, std::string n1,
- int v2 = 0, std::string n2 = "",
- int v3 = 0, std::string n3 = "",
- int v4 = 0, std::string n4 = "",
- int v5 = 0, std::string n5 = "",
- int v6 = 0, std::string n6 = "",
- int v7 = 0, std::string n7 = "",
- int v8 = 0, std::string n8 = "",
- int v9 = 0, std::string n9 = "",
- int v10 = 0, std::string n10 = "",
- int v11 = 0, std::string n11 = "",
- int v12 = 0, std::string n12 = "",
- int v13 = 0, std::string n13 = "",
- int v14 = 0, std::string n14 = "",
- int v15 = 0, std::string n15 = "",
- int v16 = 0, std::string n16 = "",
- int v17 = 0, std::string n17 = "",
- int v18 = 0, std::string n18 = "",
- int v19 = 0, std::string n19 = "",
- int v20 = 0, std::string n20 = "",
- int v21 = 0, std::string n21 = "",
- int v22 = 0, std::string n22 = "");
该函数可以传入 21 个及以下个不同的枚举类型值, 并且每个值都对应一个相应的字符串名字, 以后可以直接输出该字符串的名字, 以方便调试. 此外, 这个字符串名字还可以用于使用字符串值类型对属性赋值.
2.6 对象指针
- MakePointerChecker ();//RandomVariableStream 就是属性可以设置的对象类型
- ##### 2.6.1 单个对象
- Ptr<MyAnotherObject> m_object;
- TypeId
- MyObject::GetTypeId ()
- {
- static TypeId tid = TypeId("ns3::MyObject") // 创建 TypeId,
- .SetParent(Object::GetTypeId())
- .SetGroupName("MyExample")
- .AddConstructor<MyObject>()
- .AddAttribute ("myObject", "help text",
- PointerValue(0),
- MakePointerAccessor (&MyObject::m_object),
- MakePointerChecker <MyAnotherObject>())
- ;
- return tid;
- }
- int
- main (int argc, char *argv[])
- {
- LogComponentEnable("MyObject", LOG_LEVEL_LOGIC);
- Ptr<MyAnotherObject> aObj = CreateObject<MyAnotherObject>();
- aObj->SetAttribute("myValue", StringValue("5"));
- Ptr<MyObject> obj = CreateObject<MyObject>();
- obj->SetAttribute("myObject", PointerValue(aObj));
- PointerValue pointerValue;
- obj->GetAttribute("myObject", pointerValue);
- Ptr<MyAnotherObject> aObj2 = pointerValue.Get<MyAnotherObject>();
- UintegerValue myValue;
- aObj2->GetAttribute("myValue", myValue);
- uint32_t value = myValue.Get();
- NS_LOG_UNCOND(value);
- }
2.6.2 多个对象
在 NS-3 当中用来表示集合值的属性类型是 ObjectPtrContainerValue, 其主要作用是存储多个对象的智能指针. 为了匹配 C++ 当中的集合类型, NS-3 将 ObjectPtrContainerValue 重定义成了两种具体的类型: ObjectVectorValue 和 ObjectMapValue. 并对两种不同的具体类型提供了不同的方法支持.
2.6.2.1. ObjectVectorValue
ObjectVectorValue 也同时提供了两种访问器: 变量访问器和函数访问器. 变量访问器的使用和其他类型的变量访问器一致. 函数访问器如下所示:
- uint32_t DoGetVectorN (void) const {
- return m_vector2.size ();
- }// 返回了 vector 当中元素的个数
- Ptr<Derived> DoGetVector (uint32_t i) const {
- return m_vector2[i];
- }// 返回了 vector 的第 i 个元素
定义 vector 属性
- .AddAttribute ("TestVector2", "help text",
- ObjectVectorValue (),
- MakeObjectVectorAccessor (&AttributeObjectTest::DoGetVectorN,
- &AttributeObjectTest::DoGetVector),
- MakeObjectVectorChecker<Derived> ())
ObjectVectorValue 属性是一个只读属性: 通过属性的方式仅仅能读取其中的值, 如果想改变 vector 当中的元素值, 或者改变 vector 元素本身, 那么必须改变对象中的成员变量本身, 此时, 对应的属性值也会改变.
vector 当中的对象一定是以智能指针 Ptr 来表示的
vector 当中的对象一定继承自 NS-3 的 Object 类
vector 属性仅具有读取的功能
无法设置 vector 类型的属性
每次改变 vector 后, 必须重新获取属性才能得到修改后的属性
还需要注意的是, vector 属性是无法用 StringValue 属性值创建的
- std::vector<Ptr<MyAnotherObject>> m_objects;
- .AddAttribute ("myObjects", "help text",
- ObjectVectorValue(),
- MakeObjectVectorAccessor (&MyObject::m_objects),
- MakeObjectVectorChecker <MyAnotherObject>())
- ;
- 2.6.2.2 ObjectmapValue
ObjectMapValue 具有和 ObjectVectorValue 类似的特性(ObjectMapValue 当中的键必须是整型, 不能是其他类型):
是只读属性
改变之后要重新获取属性
无法从 StringValue 创建
Map 当中的值必须是 Object 类, 并且必须使用 Ptr 智能指针表示
3. 对象工厂
对象工厂机制用来批量创建拥有同样属性的对象
3.1 对象工厂的使用
ObjectFactory 使用非常简单:
创建一个 ObjectFactory.
如果构造函数未指定 TypeId, 则可以通过 SetTypeId()方法指定一个需要创建的对象的 TypeId.
使用 Set 方法设置对象的属性值, 如果属性在该 TypeId 当中不存在, 则将抛出异常.
反复调用 Create()方法创建对象, 这些对象都具有同样的属性值.
NS-3 还提供了另外一个实用的函数, 可以在创建 Object 的同时指定属性, 可以同时指定 1 到 9 个属性:
- template <typename T>
- Ptr<T>
- CreateObjectWithAttributes
- (std::string n1 = "", const AttributeValue & v1 = EmptyAttributeValue (),
- std::string n2 = "", const AttributeValue & v2 = EmptyAttributeValue (),
- std::string n3 = "", const AttributeValue & v3 = EmptyAttributeValue (),
- std::string n4 = "", const AttributeValue & v4 = EmptyAttributeValue (),
- std::string n5 = "", const AttributeValue & v5 = EmptyAttributeValue (),
- std::string n6 = "", const AttributeValue & v6 = EmptyAttributeValue (),
- std::string n7 = "", const AttributeValue & v7 = EmptyAttributeValue (),
- std::string n8 = "", const AttributeValue & v8 = EmptyAttributeValue (),
- std::string n9 = "", const AttributeValue & v9 = EmptyAttributeValue ()
- );
- ObjectFactory oFactory;
- oFactory.SetTypeId("ns3::MyObject");
- oFactory.Set("intValue", StringValue("100"));
- oFactory.Set("booleanValue", StringValue("true"));
- oFactory.Set("doubleValue", StringValue("22.5"));
- Ptr<MyObject> obj1 = oFactory.Create<MyObject>();
- Ptr<MyObject> obj2 = oFactory.Create<MyObject>();
- Ptr<MyObject> obj3 = oFactory.Create<MyObject>();
使用 ObjectFactory 三次创建出的对象均不是同一个对象, 但是三个对象却拥有同样的属性值. 因此, 使用 ObjectFactory 非常适合批量地创建属性值相同的对象.
3.2. 从 StringValue 创建对象
来源: http://www.bubuko.com/infodetail-3343422.html