对象模型
理解程序设计的最佳切入点?——对象模型
对象模型的作用?
一、单个类的管理。(基类与子类)
 尽管每个类表示的网络元素不同,但它们往往都具有某些相同的功能需求,如动态内存管理和属性配置等。没必要在每一个类中分别实现这些需求。因此需要一些顶层的基类来统一定义与实现这些共性特征。而子类只需关注自身专属特性即可。
二、多个类的管理。(关联各个类)
 任何一个单一的类时无法完成网络模拟的。这些类需要被有机地关联起来,形成一个可以处理各种网络事件的独立主题。例如,一个网络结点需要整合应用程序、通信信道、网络设备和TCP/IP层协议等多个C++类才能与其他结点进行通信。这些类之间如何灵活、高效的关联,也是对象模型解决的问题之一。
如何实现上述目的?
定义三个基类:SimpleRefCount、ObjectBase和Object。
SimpleRefCount:解决针对单个类的动态内存管理问题,智能指针的实现。
ObjectBase解决单个类需要配置属性和trace变量的需求。
Object类解决了多个类之间动态关联问题。
 这种类之间的动态关联是通过一种叫做对象聚合的功能实现的。简单地说,对象聚合能够让一个Object对象在运行时动态地关联其他对象,而不是在编译时关联。
下图给出了上面三个基类的继承关系,以及它们各自的一些子类代表。箭头指向的类是基类。
 
智能指针
为什么使用智能指针?
传统的动态内存是从new运算符创建到被delete运算符销毁。尽管这种设计增加了内存管理的灵活度,但同时造成了两个严重的安全隐患。
内存泄漏:动态内存没有被及时释放
 空悬指针:在尚有其他指针引用的情况下释放动态内存
智能指针如何解决这两个安全隐患的呢?
除了与常规指针有着相似的行为(拷贝、赋值等),更重要的是其能够自动释放那些没有指针指向的动态内存,从而避免了内存泄漏和空悬指针问题,提高了程序的安全性。
如何实现智能指针?
智能指针由Ptr和SimpleRefCount两个类模板组成。
 其中,SimpleRefCount类是ns-3智能指针的内部实现。它定义了一个引用计数器来记录指向自身内存的指针数量。
 Ptr类则定义了ns-3智能指针的外部接口。
设计原理
Ptr使用范式
Ptr<类名>指针变量名
Ptr的实现
两部分:
 第一部分负责保存原始指针和模拟原始指针操作(复制、赋值),这一部分通过Ptr实现;
 第二部分负责记录所有指向所分配的对象内存的指针数量(引用计数器),这一部分通过SimpleRefCount实现。
下面的代码中,模板形参T(typename T)是Ptr指针指向对象的类名称。指向对象内存的原始指针m_ptr保存在Ptr类中。
 SimpleRefCount类定义了一个引用计数器m_count变量。
这段代码还包括SimpleRefCount类的Ref()和Unref()函数,分别负责计数器值的增减,当计数器变为0时,意味着没有任何指针指向该内存,这时,Unref()函数会调用删除函数销毁对象。
 
 类模板
Ref()和Unref()函数怎么使用?
定义一个SimpleRefCount的子类MyTest。Ptr指针t1和t2分别指向两个MyTest对象。
Ptr<MyTest>t1 = Create<MyTest>();
Ptr<MyTest>t2 = Create<MyTest>();
t2 = t1; //t1指向t2???
 
由于赋值操作使得MyTest_2获得了一个新的Ptr指针t1,因此t1在指向新内存之后立即调用MyTest_2的Ref()函数,使其计数器加1,变为2。相反,MyTest_1对象在这个过程中失去了原来的Ptr指针t1,因此在t1指向新内存之前会调用MyTest_1的Unref()函数,使其计数器减1。此时,MyTest_1的计数器会变为0,被销毁。

使用实例
- 初始化
 
Packet *p = NULL;
Ptr<Packet>ptr;
 
- 创建对象
 
//使用Packet()构造函数
Packet *p      = new Packet();
Ptr<Packet>ptr = Create<Packet>();
//使用Packet(uint32_t size)构造函数
Packet *p      = new Packet(100);
Ptr<Packet>ptr = Create<Packet>(100);
 
- 赋值操作
 
Packet *p        = new Packet();
Ptr<Packet>ptr_1 = p;
Ptr<Packet>ptr_2 = ptr; //???
 
- 指针运算
 
ptr->GetUid();    //->
(*ptr).GetUid();  //*
 
一个是指针指向的函数,一个是指针解引用之后的函数,但这两行最后都会执行函数,效果相同。
- 比较运算
 
if(ptr == ptr_1){}
if(ptr != ptr_1){}
if(ptr == p){}
if(ptr != p){}
 
等于和不等于符号可以用于两个Ptr指针之间,也可以用于一个Ptr指针和一个原始指针之间。
if(ptr < ptr_1){}
if(ptr <= ptr_1){}
if(ptr > ptr_1){}
if(ptr >= ptr_1){}
 
- 流插入
 
std::cout<<"address:"<<ptr<<std::endl;
 
输出Ptr指针中原始指针的地址。
- 拷贝
Ptr指针的拷贝有两种:指针拷贝和对象拷贝。 
指针拷贝:
 被拷贝的和新的Ptr指针均指向同一个对象。
Packet *p = new Packet();
Ptr<Packet>ptr(p);
Ptr<Packet>ptr_1(ptr); //Packet对象计数器加1
 
对象拷贝:
 使用的场景:不只需要一个新的指针,还需要拷贝出一个新的对象,开辟新的内存空间。这时候就需要用到Copy()函数。
 使用比较少。
Ptr<Packet>ptr = Create<Packet>();
Ptr<Packet>ptr_1 = Copy(ptr);//ptr_1指向一个新创建的Packet对象
 
- 类型转换
ns-3提供了DynamicCast()、StaticConst()和ConstCast()三个Ptr指针类型转换函数。
注意:每次调用这些函数后,都需要检查其返回Ptr指针是否为空,确认转换是否成功。 
//DynamicCast
Ptr<NetDevice>pDev = Create<LoopbackNetDevice>();
Ptr<LoopbackNetDevice>pLoopDev = DynamicCast<LoopbackNetDevice>(pDev);
if(!pLoopDev )
	NS_LOG_UNCOND("DynamicCast failure");
//StaticCast
Ptr<LoopbackNetDevice>pLoopDev1 = StaticCast<LoopbackNetDevice>(pDev);
if(!pLoopDev1 )
	NS_LOG_UNCOND("StaticCast failure");
//ConstCast
Ptr<Packet const>ptr = Create<Packet>();
Ptr<Packet>ptr_1 = ConstCast<Packet>(ptr);
if(!ptr_1)
	NS_LOG_UNCOND("ConstCast failure");
 
- 获取原始指针
获取原始指针有两个函数:PeekPointer()和GetPointer()。
GetPointer()会对Ptr指针指向对象的计数器加1。相当于新创建了一个指针指向这个对象。使用极少。
PeekPointer()则没有这个要求。 
Ptr<Packet>ptr = Create<Packet>();
Packet *p = PeekPointer(ptr);
 
适用范围
Ptr的作用:
 提供了和原始指针相同的操作集,实现了内存的动态管理。
局限性:
- 只能支持沟槽函数参数少于或等于7个的类。
 - 只能引用与SimpleRefCount子类对象。
 










