ns-3-manual.pdf 對(duì)象模型翻譯
1.6 對(duì)象模型 Object model
ns-3 本質(zhì)上是一個(gè)基于 C++對(duì)象的系統(tǒng)。對(duì)象可以像往常一樣依照 C++的規(guī)則
被聲明以及實(shí)例化。ns-3 還給傳統(tǒng)的 C++對(duì)象添加了一些新的特色來(lái)提供更強(qiáng)
的功能。本章為讀者介紹 ns-3 的對(duì)象模型。
本節(jié)描述針對(duì) ns-3 的對(duì)象的 C++類設(shè)計(jì)。總的來(lái)說(shuō),已經(jīng)使用的設(shè)計(jì)模式包括
經(jīng)典的 object-oriented 設(shè)計(jì)(多態(tài)的接口和實(shí)現(xiàn))、接口與實(shí)現(xiàn)相分離、非虛
擬的公共接口設(shè)計(jì)模式、對(duì)象聚合設(shè)施以及 reference counting for memory
management。盡管 ns-3 的設(shè)計(jì)與上述的任意一個(gè)都不嚴(yán)格一致,但熟悉類似
COM 或 Bonobo 等構(gòu)件模型的人將能夠識(shí)別 ns-3 對(duì)象聚合模型中的設(shè)計(jì)元素。
1.6.1 面向?qū)ο笮袨?Object-oriented behavior
一般而言,C++對(duì)象提供常見(jiàn)的面向?qū)ο蠊δ?抽象、封裝、繼承以及多態(tài)),
這些功能是經(jīng)典的面相對(duì)象設(shè)計(jì)的一部分。ns-3 對(duì)象使用了這些特性。例如:
class Address
{
public:
Address ();
Address (uint8_t type, const uint8_t *buffer, uint8_t len);
Address (const Address & address);
Address &operator = (const Address &address);
...
private:
uint8_t m_type;
uint8_t m_len;
...
};
1.6.2 對(duì)象的基類 Object base classes
ns-3 中有 3 個(gè)特殊的基類。由 3 個(gè)基類繼承的類能夠用特殊的特性來(lái)實(shí)例化對(duì)
象。這 3 個(gè)基類是:
class Object
class ObjectBase
class RefCountBase
沒(méi)有要求 ns-3 的對(duì)象都繼承自這 3 個(gè)類,
但對(duì)于具有特殊特性的類,是這樣的。
由 class Object 派生的類具有如下特性。
ns-3 的類型和屬性系統(tǒng)(參看 Attributes)。
對(duì)象聚合系統(tǒng)
a smart-pointer reference counting system (class Ptr)
由 class ObjectBase 派生的類具有上述前兩個(gè)特性,不具有 smart pointers。
由class RefCountBase 派生的類只具有 the smart-pointer reference counting system.
在實(shí)際中,ns-3 的開(kāi)發(fā)者碰到最多的是上述 3 個(gè)中的 class Object。
1.6.3 內(nèi)存管理和類 Ptr Memory management and class Ptr
C++程序中的內(nèi)存管理是一個(gè)復(fù)雜的過(guò)程,并且經(jīng)常被不正確地處理或者被不一
致地處理。以下介紹我們決定使用的一個(gè)引用計(jì)數(shù)設(shè)計(jì)。
所有使用引用計(jì)數(shù)的對(duì)象都維護(hù)一個(gè)內(nèi)部引用值,根據(jù)該值來(lái)決定對(duì)象什么時(shí)候
可以安全地刪除他自身。每當(dāng)有接口獲得對(duì)象的指針時(shí),該對(duì)象的引用計(jì)數(shù)值就
增加 1,這個(gè)增加是通過(guò)調(diào)用 calling Ref() 完成的。當(dāng)用戶不再使用該指針時(shí),
該指針的用戶負(fù)責(zé)顯式調(diào)用 Unref() 來(lái)解除對(duì)該指針的引用。當(dāng)引用計(jì)數(shù)減到 0
時(shí),對(duì)象被刪除。
? 當(dāng)用戶代碼通過(guò)創(chuàng)建對(duì)象從該對(duì)象獲得指針或者通過(guò) QueryInterface 獲得指針時(shí),沒(méi)有必要增加引用計(jì)數(shù)值。
? 當(dāng)用戶代碼從其他的源(比如,對(duì)指針進(jìn)行復(fù)制)獲得指針時(shí),用戶代碼必須調(diào)用 Ref() 來(lái)增加引用計(jì)數(shù)值。
? 該對(duì)象的指針的所有用戶都必須調(diào)用 Unref() 來(lái)釋放引用。
通過(guò)使用下邊描述的 reference counting smart pointer class,調(diào)用 Unref() 的
負(fù)擔(dān)有了一些緩解。
通過(guò)底層 API,并想在堆上顯式分配 non-reference-counted 對(duì)象的用戶使用
new 操作符,用戶負(fù)責(zé)刪除這類對(duì)象。
Reference counting smart pointer (Ptr)
引用計(jì)數(shù)智能指針(Ptr)Reference counting smart pointer (Ptr)
因?yàn)槭冀K調(diào)用 Ref() 和 Unref() 很麻煩,所以 ns-3 提供類似于
Boost::intrusive_ptr 的智能指針 class Ptr。該智能指針類假定底層類型提供一
對(duì) Ref 和 Unref 方法,且該對(duì)方法增加和減少對(duì)象的內(nèi)部引用計(jì)數(shù)值。
這種實(shí)現(xiàn)使得你能夠像操縱普通指針一樣操縱智能指針:可以將他和 0 比較、
將他和其他指針比較以及給他賦 0 值,等等。
通過(guò) GetPointer 和 PeekPointer 方法有可能從智能指針中提取出裸指針。
如果你想把用 new 產(chǎn)生的對(duì)象存儲(chǔ)到一個(gè)智能指針。為了避免內(nèi)存泄漏,我們
建議你使用 CreateObject 模板函數(shù)來(lái)創(chuàng)建對(duì)象并將他存儲(chǔ)到智能指針。這些函
數(shù)是很小的便利函數(shù),他們的目標(biāo)僅僅是使你少敲些鍵盤(pán)。
1.6.4 CreateObject 和 Create CreateObject and Create
C++的對(duì)象可以被靜態(tài)地創(chuàng)建、動(dòng)態(tài)地創(chuàng)建以及自動(dòng)地創(chuàng)建。這在 ns-3 中同樣
適用,但系統(tǒng)中的一些對(duì)象有一些附加的框架。特別地,引用計(jì)數(shù)的對(duì)象通常使
用模板化的 Create 或 CreateObject 方法被分配。
對(duì)于由 class Object 派生的對(duì)象:
Ptr<WifiNetDevice> device = CreateObject<WifiNetDevice> ();
不要使用 operator new 來(lái)創(chuàng)建這類對(duì)象。應(yīng)該使用 CreateObject() 來(lái)創(chuàng)建。
對(duì)于由 class RefCountBase 派生的對(duì)象,或其他支持智能指針類用法的對(duì)象
(特別地,比如 ns-3 Packet class),建議使用模板化的 helper function:
Ptr<B> b = Create<B> ();
這是一個(gè)對(duì) new 操作符的封裝,他正確地處理了引用計(jì)數(shù)系統(tǒng)。
1.6.5 聚合 Aggregation
ns-3 的對(duì)象聚合系統(tǒng)很大程度上是由一個(gè)認(rèn)識(shí)促成的,即 ns-2 中一個(gè)很普遍
的用法是通過(guò)繼承和多態(tài)來(lái)擴(kuò)展協(xié)議模型。例如,TCP 的特殊版本
RenoTcpAgent 是由類 TcpAgent 派生的,并對(duì)基類的函數(shù)進(jìn)行覆蓋(override)。
盡管如此,ns-2 模型中出現(xiàn)的兩個(gè)問(wèn)題是 downcasts 和“weak base class” 。
Downcasting 是指一個(gè)過(guò)程,即使用指向某個(gè)基類對(duì)象的指針并在程序運(yùn)行時(shí)查
詢?cè)撝羔榿?lái)獲得類型信息,然后將該指針顯式轉(zhuǎn)換為子類的指針,以便子類的
API 能夠使用。
Weak base class 是指當(dāng)某個(gè)類無(wú)法被有效地重用(由他進(jìn)行派
生)出現(xiàn)的問(wèn)題,因?yàn)樗鄙俦匾墓δ?導(dǎo)致開(kāi)發(fā)者不得不修改基類,這將導(dǎo)
致基類 API 的增生,某些 API 可能并不是對(duì)所有子類都在語(yǔ)義上正確。
ns-3 使用查詢接口設(shè)計(jì)模式來(lái)避免這些問(wèn)題。該設(shè)計(jì)基于 Component Object
Model 和 GNOME Bonobo 的基礎(chǔ)。盡管現(xiàn)在替代構(gòu)件的完全的二進(jìn)制兼容性
還不被支持,但我們努力簡(jiǎn)化語(yǔ)法和對(duì)模型編寫(xiě)者的影響。
1.6.6 聚合的例子 Aggregation example
ns-3 中,class Node 是使用聚合的一個(gè)很好的例子。注意 ns-3 中沒(méi)有類 Node
的派生類(比如類 InternetNode 等),而是將構(gòu)件(各種協(xié)議)聚合到節(jié)點(diǎn)中。
我們來(lái)研究一些 Ipv4 協(xié)議是如何被加入節(jié)點(diǎn)的。
static void
AddIpv4Stack(Ptr<Node> node)
{
Ptr<Ipv4L3Protocol> ipv4 = CreateObject<Ipv4L3Protocol> ();
ipv4->SetNode (node);
node->AggregateObject (ipv4);
Ptr<Ipv4Impl> ipv4Impl = CreateObject<Ipv4Impl> ();
ipv4Impl->SetIpv4 (ipv4);
node->AggregateObject (ipv4Impl);
}
注意 Ipv4 協(xié)議是用 CreateObject() 創(chuàng)建的。接著 Ipv4 協(xié)議被聚合到節(jié)點(diǎn)中。
這樣,基類 Node 就不需要被編輯來(lái)使得用戶使用指向基類 Node 的指針來(lái)訪問(wèn)
Ipv4 接口;用戶可以在程序運(yùn)行時(shí)來(lái)向節(jié)點(diǎn)請(qǐng)求指向該節(jié)點(diǎn)的 Ipv4 接口的指針。
用戶如何向節(jié)點(diǎn)提出請(qǐng)求在下一小節(jié)描述。
注意:將多于一個(gè)的同一類型的對(duì)象聚合到某個(gè) ns3::object 是編程錯(cuò)誤。所以,
如果想要存儲(chǔ)一個(gè)節(jié)點(diǎn)的所有活動(dòng)的 sockets,聚合是不可選的。
GetObject 的例子 GetObject example
GetObject 是一個(gè)獲得安全 downcasting 的類型安全的方法,并且使得接口能夠
在對(duì)象上被找到。
考慮一個(gè)節(jié)點(diǎn)的指針 m_node,該指針指向一個(gè)節(jié)點(diǎn)對(duì)象,且先前已經(jīng)將 Ipv4
的實(shí)現(xiàn)聚合到了該節(jié)點(diǎn)。客戶代碼想要配置一個(gè)默認(rèn)的路由。為了實(shí)現(xiàn)這點(diǎn),必
須訪問(wèn)該節(jié)點(diǎn)內(nèi)的一個(gè)對(duì)象,且該對(duì)象具有 IP 轉(zhuǎn)發(fā)配置的接口。如下:
Ptr<Ipv4> ipv4 = m_node->GetObject<Ipv4> ();
如果實(shí)際上沒(méi)有 Ipv4 的對(duì)象被聚合到該節(jié)點(diǎn),那么該方法將返回 null。因此,
檢查該函數(shù)調(diào)用的返回值是一個(gè)好習(xí)慣。如果成功,則用戶可以使用 Ptr,該指
針指向先前被聚合到該節(jié)點(diǎn)的 Ipv4 對(duì)象。
另一個(gè)如何使用聚合的例子是給對(duì)象添加可選的模型。例如,一個(gè)現(xiàn)存的 Node
對(duì)象可以具有一個(gè)在運(yùn)行時(shí)被聚合到該節(jié)點(diǎn)對(duì)象的“Energy Model”對(duì)象(不需要
對(duì)節(jié)點(diǎn)類進(jìn)行修改和重新編譯)。一個(gè)現(xiàn)存的模型(比如一個(gè)無(wú)線網(wǎng)絡(luò)設(shè)備)可
以通過(guò)”GetObject”來(lái)獲得該能量模型并表現(xiàn)地就像該接口是內(nèi)建在 Node 對(duì)象
的底層或者該接口是在運(yùn)行時(shí)被聚合到該節(jié)點(diǎn)的。而其他節(jié)點(diǎn)卻不需要知道能量
模型的任何事情。
我們希望這樣的編程模式可以大量減小開(kāi)發(fā)者修改各種基類的必要。
1.6.7 Object factories
一個(gè)常見(jiàn)的用法例子是創(chuàng)建許多相似的配置對(duì)象。你可以重復(fù)調(diào)用CreateObject(),
但是在ns-3中也有一個(gè)工廠設(shè)計(jì)模式。它已經(jīng)在“helper” API中已經(jīng)大量使用了。
ObjectFactory類可以用來(lái)初始化對(duì)象,和配置這些對(duì)向的屬性:
void SetTypeId (TypeId tid);
void Set (std::string name, const AttributeValue &value);
Ptr<T> Create (void) const;
第一個(gè)方法允許你使用ns-3 TypedId系統(tǒng)來(lái)具體指定創(chuàng)建對(duì)象的類型。
第二個(gè)方法允許你設(shè)置要?jiǎng)?chuàng)建對(duì)象的屬性。
第三個(gè)方法允許你使用工廠對(duì)象自己創(chuàng)建對(duì)象。
例如:
ObjectFactory factory;
// Make this factory create objects of type FriisPropagationLossModel
factory.SetTypeId ("ns3::FriisPropagationLossModel")
// Make this factory object change a default value of an attribute, for
// subsequently created objects
factory.Set ("SystemLoss", DoubleValue (2.0));
// Create one such object
Ptr<Object> object = factory.Create ();
factory.Set ("SystemLoss", DoubleValue (3.0));
// Create another object with a different SystemLoss 再次創(chuàng)建一個(gè)對(duì)戲那個(gè)
Ptr<Object> object = factory.Create ();
1.6.8 Downcasting
有一個(gè)經(jīng)常出現(xiàn)的問(wèn)題:”假設(shè)我有一個(gè)基類的指針(Ptr),該指針指向一個(gè)對(duì)
象,如果我想要派生類的指針,那么我應(yīng)該進(jìn)行 downcast(通過(guò) C++的動(dòng)態(tài)類
型轉(zhuǎn)換)來(lái)獲得派生的指針還是應(yīng)該使用對(duì)象聚合系統(tǒng)進(jìn)行 GetObject<> () 來(lái)
找到一個(gè) Ptr,該指針指向子類 API 的接口”?
這個(gè)問(wèn)題的答案是:在多數(shù)情況下,兩種技術(shù)都行的通。ns-3 提供一個(gè)模板化
的函數(shù),該函數(shù)使得對(duì)象動(dòng)態(tài)類型轉(zhuǎn)換的語(yǔ)法更加友好:
template <typename T1, typename T2>
Ptr<T1>
DynamicCast (Ptr<T2> const&p)
{
return Ptr<T1> (dynamic_cast<T1 *> (PeekPointer (p)));
}
當(dāng)程序員有一個(gè)基類的指針,想和一個(gè)子類的指針進(jìn)行測(cè)試時(shí),DynamicCast
行的通。當(dāng)尋找被聚合的不同對(duì)象時(shí), GetObject 行的通。但對(duì)于子類, GetObject
也行的通,和 DynamicCast 一樣。如果不確定,那么程序員應(yīng)該使用 GetObject,
因?yàn)樗谒星闆r下都適用。如果程序員知道所考慮的對(duì)象的類層次結(jié)構(gòu),使用
DynamicCast 更加直接。