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非常適合批量地創建屬性值相同的對象。