我是一个初学者,想要建一个游泳池。我做了一些研究,发现如果我的软件有沉重的对象创建和删除部分,我应该使用池,因为我的性能。我做了一些功能,从池中获取一个T,并重新设置它,并想知道它是否正确工作。我尝试了我的实现,但似乎有点慢。我怎样才能改善这一点?在数组中在堆栈上分配Ts的池,每个正在使用的对象都有一个存储的指针,空闲()通过指针重置对象。我也做了一个完全重置的功能。我发现我应该使用一个智能指针,但首先我想用自己的指针构建一个简单的池。作为第一个改进,我想为向量使用一个新的位置,以避免使用堆。
#include <array>
#include <vector>
#include <algorithm>
#include <iostream>
//------------------------------------------------------------------------------
template<class T,int N>
class Pool
{
public:
Pool();
T* get();
void free(T* t);
void reset();
int available()const{ return N - used.size(); } // number of free Ts
private:
T default_T{}; // reset helper
std::array<T,N> allocated{};
std::vector<T*> used;
};
//------------------------------------------------------------------------------
template<class T, int N>
T* Pool<T,N>::get()
// get a T from the pool; return 0 if no free Ts
{
if(available()<1)
{
std::cerr << "Pool exhausted! Free memory!\n";
return 0;
}
T* t{nullptr};
for(unsigned int i=0; i<allocated.size(); ++i)
{
auto p = std::find(used.begin()+i,used.end(),&allocated[i]);
if(p==used.end())
{
t = &allocated[i];
used.push_back(t);
return t;
}
}
return t;
}
//------------------------------------------------------------------------------
template<class T,int N>
void Pool<T,N>::free(T* t)
// return a T given out by get() to the pool and reset it to default
{
auto p = std::find(used.begin(),used.end(),t);
if(*p==t)
{
*t = default_T;
used.erase(p);
}
}
//------------------------------------------------------------------------------
template<class T,int N>
void Pool<T,N>::reset()
// return ALL T given out by get() to the pool
{
for(auto& r: allocated)
free(&r);
}发布于 2016-06-17 20:09:29
我要指出的第一点是,所有容器都可以定制为使用替代分配器(比如池分配器)。因此,我将尝试编写一个使用池的标准分配器,而不是编写一些非常定制的东西。
第二点。对于创建和销毁小对象,C++进行了高度优化。除非您的类非常特殊,否则标准的分配器将非常快,击败它们将是困难的。
您的分配器在分配和取消分配中都会在整个池中进行线性搜索。坦率地说,这是相当糟糕的,你的分配时间只会变得更糟,因为你的池变大了。
还可以返回已经构造的对象。当对象被释放时,它们不会被销毁(因此您得到的对象状态可能是不确定的)。我见过的大多数分配器系统都会给出一些正确对齐的原始字节,并期望您返回原始字节(即已调用了析构函数)。在返回对象之前,不能调用析构函数,因为get返回已构造的对象。
不要写这样的评论
// get a T from the pool; return 0 if no free Ts我想我可以从名字中看出这一点。你的评论应该解释为什么不是什么。我应该能够从写得好的代码中看出这一点。问题在于,如果代码和注释描述的是什么,那么随着时间的推移(当应用bug修复时)可能会出现差异。如果你解释一下为什么通常情况会保持不变。
如果我现在将代码更改为返回nullptr,那么注释和代码是不同的,但它仍然有效。下一个人是否更改代码或注释。
// I would throw an exception
std::cerr << "Pool exhausted! Free memory!\n";
return 0;错误代码在包含有自己的单元测试的类中很好。但是,逃避接口边界的错误代码是个坏主意。它假设使用您的代码的人与您一样优秀,并将检查错误代码并采取适当的操作(提示:他们不会)。抛出一个异常。这样,如果他们忘记检查错误,代码就不会中断。
这看起来像一个O(N^2)循环。
T* t{nullptr};
for(unsigned int i=0; i<allocated.size(); ++i)
{
auto p = std::find(used.begin()+i,used.end(),&allocated[i]);
// STUFF
}为什么不只是一个find()呢?
更好的是为什么要搜索。有一份免费的名单。将标题项目从空闲列表中删除并返回。当返回一个值时,将其添加回空闲列表的首。
注意:我不是在说一个明确的列表结构。您可以拥有一个对象数组。以及一系列的索引值。然后,空闲项数组包含列表中的下一个元素。
// Example of a free list.
std::array<T, N> objects;
std::array<int, N> freeList;
int freeListHead;
Pool()
: freeListHead(0)
{
for(int loop = 0; loop < N; ++loop)
{
freeList[loop] = loop + 1;
}
}
T* get()
{
T* result = &objects[freeListHead];
freeListHead = freeList[freeListHead];
}
void put(T* obj)
{
int offset = std::distance(&objects[0], obj);
freeList[offset] = freeListHead;
freeListHead = offset;
}的性质
您假设T有一个copy assignment operator。在这个充满可移动物体的美丽的新世界里,情况并非如此。
*t = default_T;重置中的
void Pool<T,N>::reset()
{
for(auto& r: allocated)
free(&r);
}这似乎是合乎逻辑的。但是free()包含一个find()。因为您需要重置所有项目,所以您可以先这样做,然后jsut重置包含迭代器的容器,然后就会产生相同的影响。
void Pool<T,N>::reset()
{
for(auto& r: allocated)
*r = default_T;
allocated.reset();
}https://codereview.stackexchange.com/questions/132224
复制相似问题