第八十三讲 对象池(2)

好吧,回头去看看我们的上一讲,内容可以说深也可以说不深,当然看大家的理解了,同时留给大家思考的问题,都这么久了,想必大家心中都有数了吧。不管大家怎么想的,是否都理清楚了,我还是觉得我有必要将没有完成的内容被补上。

我们上一讲说了一个基类,一个能够在蔟中分配对象的基类。哦,对了记得以前有同学问,为什么要把这个基类的虚构函数声明为protected,嗯,这个嘛,简单点说,就是我们不能在栈上分配对象,我们只能在堆上分配,当然这正是我们想要的,一个长期运行的程序,他的相关数据变量最好都是在堆上分配,这是簇的用处,当然可以认为这就是一个简单的对象池。

关于基类,还是如同上一讲我们所说的,暂且先那样吧,我们先来看看所谓的簇ObjPool,从C++的设计理念,我们不用考虑什么,我们可以得出ObjPool的原始相貌像下面这样:
—————————————
class ObjPool{
frend class ObjPoolItem;


public:

ObjPool();



~ObjPool();


private:

ObjPool(const ObjPool&

);



ObjPool&

operator=(const ObjPool&

);


};


——————————————
如果不是具体实现,我们也实在想不出还有那些需要的成员,所以接下来的我们就应该尝试着去实现,只有在实现的过程中我们可以发现问题的所在。

说到实现,不知道大家有没有想过对象是怎么储存在池中的,是随意乱扔呢还是有序摆放?当然不可以随意乱扔的了,因为那样不利于管理,当然也不该是有序摆放的了,因为那样在或多或少会有效率上的损失(如果不明白这句话的意思,那么可以回头去看看我们说标准库的时候说的序列容器),那么这个对象会是怎么摆放的呢?其实他是无序的同时也是有序的,其实卖了这多关子,说到底就是一个链表的形式,所以,我们应该有一个对象的指针:
———————————–
class ObjPool{
frend class ObjPoolItem;


public:

ObjPool();



~ObjPool();


private:

ObjPoolItem* head;

//保存对象的指针

ObjPool(const ObjPool&

);



ObjPool&

operator=(const ObjPool&

);


};


——————————————
既然我们池保持了一个对象的指针(head),那么对象是不是也应该要保存着一个只想该类对象的指针呢?(回想我们说过的list),我们ObjPoolItem看上去像下面这样:
————————————
class ObjPoolItem{

friend class ObjPool;


public:

ObjPoolItem();


void* operator new(size_t,ObjPool&

);


protected:

virtual ~ObjPoolItem(){ }
private:

ObjPoolItem* next;


void* operator new(size_t);



ObjPoolItem(ObjPoolItem&

);



ObjPoolItem&

operator=(const ObjPoolItem&

);


};


————————————
到现在还觉得难吗?有了这些信息,我们可以马上定义出池的构造函数析构函数来:
————————————
ObjPool::ObjPool():head(0)
{
}

ObjPool::~ObjPool()
{

while(head){

ObjPoolItem*
temp = head->next;


delete head;

//能够删除任何一个从ObjPoolItem派生出来的类的对象

head = temp;


}
}
————————————
现在我们来考虑一个问题,那就是我们如何把对象放进池呢?当然我们还是使用new:
————————————-
ObjPool _pool;


new(_pool) T;

//T是任何一个ObjPoolItem的派生类
————————————-

当然上面这句代码调用的就是我们自定义的new操作符:
————————————–
void* operator new(size_t,ObjPool&

);


————————————–

在这里_pool被作为new操作符的第二个参数,那么现在我们是不是明白了呢?这里是我们唯一检查参数_pool是否有效的时机,但是对于学了这么C++的我们更应该明白一件事,那就是new操作符其实做的是两件事,一,分配内存,他的机制应该是调用C函数malloc,第二,构造对象,这里将会调用构造函数,所以操作符是检查池有效的唯一时机,但是我们还必须要让池在构造函数中曝光,当然,这个不难,一个静态变量就能够完成此事。
————————————
class ObjPoolItem{

friend class ObjPool;


public:

ObjPoolItem();


void* operator new(size_t,ObjPool&

);


protected:

virtual ~ObjPoolItem(){ }
private:

static ObjPool* m_pool;

//让池对象在构造函数中曝光

ObjPoolItem* next;


void* operator new(size_t);



ObjPoolItem(ObjPoolItem&

);



ObjPoolItem&

operator=(const ObjPoolItem&

);


};


————————————
现在我们可以轻松的解决掉new操作符:
————————————-
void* ObjPoolItem::operator new(size_t n,ObjPool&

pool)
{

m_pool = &

pool;


return ::operator new(n);

//全局new
}
—————————————-

如果大家对new操作符的使用有疑问的话,可以看看Effective C++第三版的第49条到52条吧,我们这里不主要谈new,我们这里主要谈怎么分配。

我们最终还是调用全局的new来分配内存和创建对象,所以现在该是我们来看看构造函数的时候了:
—————————————-
ObjPoolItem::ObjPoolItem()
{

next = m_pool->head;



m_pool->head = this;


}
—————————————–

如果我说到这里我们的任务就完成了那么大家还有什么想说的呢?大家可能还会问,对于已经存在的类,我们该怎么来分配?不要忘了C++的基类概念就是类,C++的设计理念就是继承:
—————————————-
class OldClass;

//这是一个原本就存在的class,现在我们想要将他在簇中分配,但是我们不想重新写这个类
class NewClass:

public OldClass,public ObjPoolItem
{
};

//就只要这么简单,不需要改写什么我们就将他们放入池中
int main(){

ObjPool _pool;



OldClass* _p = new(_pool) NewClass;


/////
}
—————————————-
最后,我们用一句话来总结我们这里的对象池:由问题导出解决方案也不失为一种解决方案。
如果我们不打算从头来学一遍的话,其实我们可以使用容器来完成这些事,但是或许威力比不上我们这里设计的这个,当然很多情况下是我们用不到威力这么大的工具,所以我们没有必要重新来早车轮,还有重写new操作符这本身就属于C++的高级操作,所以如果不小心,就可能导致出各种各样的问题出来了。
ok,接下来打算给大家说说C++的多线程。

============================
回复D直接查看目录

原文始发于微信公众号(

C/C++的编程教室

):第八十三讲 对象池(2)

|

发表评论