NS3-对象框架之智能指针



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使用了两个类:SimpleRefCountPtr

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的属性框架主要是通过ObjectBaseTypeId来实现的,因此要使用属性框架的类,必须继承自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创建对象

原文地址:https://www.cnblogs.com/lyszyl/p/12077914.html

时间: 2024-11-05 16:12:05

NS3-对象框架之智能指针的相关文章

读书笔记_Effective_C++_条款十七:以独立语句将new产生的对象置入智能指针

int get_int(); void f(shared_ptr<int> a, int); //下面调用 f(new int(3), get_int());//如果是类而不是int就可以会有有explicit,就不能隐式转换 f(shared_ptr<int> a = new int(3), get_int());//还有显式转换 //然而都不是好方法 //从小老师就教导我们不同的编译器,调用参数顺序是不一样的 //在调用内存时,不要烦多写几句 //*****以独立语句将new产

以独立语句将 newed 对象放入智能指针

如下的代码,虽然使用了智能指针 shared_ptr , 但是还是可能泄漏资源. processWidget(shared_ptr<Widget>(new Widget), priority()); 上述函数有两个参数,第一个是 shared_ptr, 第二个是纯粹是 priority()函数的调用. 调用时,编译器必须创建代码,包括如下三件事情: 调用 priority 执行 new Widget 调用 shared_ptr 构造函数 而三者的次序却是有 C++ 编译器来决定的,可能的调用顺

Effective C++:条款17:以独立语句将newed对象置入智能指针

(一) 假设有下面这两个函数: int priority(); void processWidget(tr1::shared_ptr<Widget> pw, int priority); 现在这样调用它: processWidget(new Widget, priority()); 但是!上面这种调用不能通过编译,因为tr1::shared_ptr构造函数需要一个原始指针,但是,这个构造函数是explicit构造函数,无法进行隐式转换. 要通过编译的话,要像下面这种调用方式: processW

Item 17:在单独的语句中将new的对象放入智能指针 Effective C++笔记

Item 17: Store newed objects in smart pointers in standalone statements. 在单独的语句中将new的对象放入智能指针,这是为了由于其他表达式抛出异常而导致的资源泄漏. 因为C++不同于其他语言,函数参数的计算顺序很大程度上决定于编译器. 如果你在做Windows程序设计,或者DLL开发,可能会经常碰到类似__cdecl,__stdcall等关键字.它们便是来指定参数入栈顺序的. 关于函数和参数的讨论可以参考:C++手稿:函数与

[017]以独立语句将newed对象置入智能指针

这一节也比较简单,先假设我们有如下的函数: int foo(); void memFoo(shared_ptr<T> pw, int foo); 现在假设我们要调用memFoo函数: memFoo(new W, foo()); 但是这样写编译是通不过的,我们可以改造为: memFoo(shared_ptr<p>(new W), foo()); 这样编译就没有问题了,但是却可能出现内存泄露问题,为什么呢? 因为编译器在产出一个memFoo调用码之前,必须先核对即将被传递的各个实参,即

Effective C++ 条款17 以独立语句将newed对象置入智能指针

  对于函数: int priority(); void processWidget(std::tr1::  shared_ptr<Widget> pw,int priority); 调用以上函数 processWidget(new Widget,priority()); 以上调用错误,因为shared_ptr构造函数需要一个原始指针,但该构造函数是个explicit构造函数,无法进行隐式转换. 而且其调用顺序也无法确定. 所以,我们一般使用分离语句,创建Widget,然后置入只能指针中.最后

Effective 学习之以独立语句将newed对象置入智能指针

时间:2014.05.23 地点:基地 --------------------------------------------------------------------------------- 一.常识 C/C++中函数在被调用时,函数的参数的执行顺序是不确定的. --------------------------------------------------------------------------------- 二.问题 假设有两个函数,一个揭示处理程序的优先权,还一个用

effective C++ 读书笔记 条款17 以独立语句讲newed对象置入智能指针

// Test.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #include <iostream> #include <memory> //注意加这个头文件 using namespace std; class Widdget { }; int priority() { return 0; } /* 下面的函数可能造成内存泄露: 调用的时候如下: processWiddget(std::tr1::shared_ptr&

effective c++ 条款17:以独立语句将newd对象置入智能指针

记住: 以独立语句将newd对象存储于智能指针内.如果不这样做,一旦异常被抛出,有可能导致难以察觉的资源泄漏. int priority(); void processWidget(std::tr1::shared_ptr<Widget> pw, int priority); //编译错误,因为shared_ptr的构造函数是个explicit构造函数,无法进行隐式转换 processWidget(new Widget, priority()); //编译正确,但有潜在问题. //在调用pro