我需要一个实现以下API的容器(不需要实现其他任何东西):
class C<T> {
C();
T& operator[](int); // must have reasonably sane time constant
// expand the container by default constructing elements in place.
void resize(int); // only way anything is added.
void clear();
C<T>::iterator begin();
C<T>::iterator end();
}
可用于:
class I {
public:
I();
private: // copy and assignment explicate disallowed
I(I&);
I& operator=(I&);
}
这种野兽的剂量存在吗?
vector<T>
没有这样做(调整大小的动作),我不确定deque<T>
有多快。
我不关心分配
有些人认为我无法复制的原因是内存分配问题。限制的原因是元素类型明确禁止复制,我无法改变它。
看起来我得到了答案:STL没有答案。但现在我想知道Why not?
答案 0 :(得分:5)
如果无法复制元素并在其他地方手动管理内存,则可以使用指针容器(如std::vector<T*>
)。
如果向量应该拥有元素,那么像std::vector< std::shared_ptr<T> >
这样的东西可能更合适。
还有Boost Pointer Container library,它为指针的异常安全处理提供容器。
答案 1 :(得分:5)
我很确定这里的答案是相当强调的“不”。根据您的定义,resize()
应该分配新存储并使用默认构造函数初始化,如果我正确读取它。然后,您将通过索引到集合并操纵引用来操纵对象,而不是“插入”到集合中。否则,您需要复制构造函数和赋值运算符。标准库中的所有容器都有此要求。
您可能希望使用boost::ptr_vector<T>
之类的内容。由于您正在插入指针,因此您不必担心复制。这需要您动态分配所有对象。
答案 2 :(得分:4)
使用deque
:表现很好。
标准说,“deque
是大多数插入和删除发生在序列的开头或结尾时所选择的数据结构”(23.1.1)。在您的情况下,所有插入和删除都在最后进行,符合使用deque
的标准。
http://www.gotw.ca/gotw/054.htm提供了一些关于如何衡量绩效的提示,尽管可能是您考虑了特定的用例,因此这就是您应该衡量的内容。
编辑:好的,如果您对deque
的反对意见实际上不是,“我不确定deque
有多快”,但“元素类型不能是标准容器中的元素“,那么我们可以排除任何标准容器。不,这样的野兽不存在。 deque
“永远不会复制元素”,但它会从其他对象复制构造它们。
接下来最好的事情可能是创建元素数组,默认构造,并维护指向这些元素的指针容器。这些方面的东西,虽然这可能会大大调整。
template <typename T>
struct C {
vector<shared_array<T> > blocks;
vector<T*> elements; // lazy, to avoid needing deque-style iterators through the blocks.
T &operator[](size_t idx) { return *elements[idx]; }
void resize(size_t n) {
if (n <= elements.size()) { /* exercise for the reader */ }
else {
boost::shared_array<T> newblock(new T[elements.size() - n]);
blocks.push_back(newblock);
size_t old = elements.size();
// currently we "leak" newblock on an exception: see below
elements.resize(n);
for (int i = old; j < n; ++i) {
elements[i] = &newblock[i - old];
}
}
void clear() {
blocks.clear();
elements.clear();
}
};
当您添加更多函数和运算符时,它将接近deque
,但避免任何需要复制类型T的内容。
编辑:想到这一点,在有人做resize(10); resize(20); resize(15);
的情况下,我的“读者练习”不能完全正确地完成。你不能半删除一个数组。因此,如果您想要正确地重现容器resize()
语义,立即破坏多余的元素,那么您将不得不单独分配元素(或熟悉新的位置):
template <typename T>
struct C {
deque<shared_ptr<T> > elements; // or boost::ptr_deque, or a vector.
T &operator[](size_t idx) { return *elements[idx]; }
void resize(size_t n) {
size_t oldsize = elements.size();
elements.resize(n);
if (n > oldsize) {
try {
for (size_t i = oldsize; i < n; ++i) {
elements[i] = shared_ptr<T>(new T());
}
} catch(...) {
// closest we can get to strong exception guarantee, since
// by definition we can't do anything copy-and-swap-like
elements.resize(oldsize);
throw;
}
}
}
void clear() {
elements.clear();
}
};
更好的代码,不那么热衷于内存访问模式(但是,我不清楚性能是否是一个问题,因为你担心deque
的速度。)
答案 3 :(得分:3)
正如您所发现的,所有标准容器都与您的要求不符。如果我们可以做出一些额外的假设,那么编写自己的容器就不会太难了。
resize
将始终以比以前更大的数量被调用,永远不会更少。resize
可以使容器大于要求的容器;在容器末端构造一些未使用的对象是可以接受的。这是一个开始。我把很多细节留给你。
class C<T> {
C();
~C() { clear(); }
T& operator[](int i) // must have reasonably sane time constant
{
return blocks[i / block_size][i % block_size];
}
// expand the container by default constructing elements in place.
void resize(int n) // only way anything is added.
{
for (int i = (current_size/block_size)+1; i <= n/block_size; ++i)
{
blocks.push_back(new T[block_size]);
}
current_size = n;
}
void clear()
{
for (vector<T*>::iterator i = blocks.begin(); i != blocks.end(); ++i)
delete[] *i;
current_size = 0;
}
C<T>::iterator begin();
C<T>::iterator end();
private:
vector<T*> blocks;
int current_size;
const int block_size = 1024; // choose a size appropriate to T
}
P.S。如果有人问您为什么要这样做,请告诉他们您需要一个std::auto_ptr
数组。那应该是笑的好。
答案 4 :(得分:2)
所有标准容器都需要可复制元素。至少因为push_back和insert复制传递给它们的元素。我不认为你可以逃脱std :: deque,因为即使它的resize方法也需要复制参数来填充元素。
要在标准容器中使用完全不可复制的类,您必须存储指向这些对象的指针。这有时可能是一种负担,但使用shared_ptr或各种boost pointer containers可以使其变得更容易。
如果你不喜欢任何这些解决方案,那么请浏览其余的提升。也许还有其他适合的东西。也许是intrusive containers?
否则,如果您认为任何这些都不符合您的需求,那么您可以随时尝试使用您自己的容器来完成您想要的任务。 (或者做更多的搜索,看看是否有其他人做过这样的事情。)
答案 5 :(得分:1)
您不应该根据它处理内存的方式来选择容器。例如deque是一个双端队列,所以你只能在需要双端队列时使用它。
如果调整大小,每个容器都会分配内存!当然,您可以通过调用vector::reserve
来预先更改容量。容量是内存中物理元素的数量,大小是您正在使用的数量。
显然,如果你超过你的容量,仍会有分配。
答案 6 :(得分:0)
看看::boost::array
。它不允许在创建容器后调整容器大小,但它不会复制任何东西。
同时获得resize
和不复制都是一招。我不相信::std::deque
因为我认为它可能会在某些情况下复制。如果你真的需要调整大小,我会编写你自己的deque-like容器。因为您要调整大小并且不进行复制的唯一方法是使用::std::deque
之类的页面系统。
此外,拥有一个页面系统必然意味着at
不会像::std::vector
和::boost::array
那样快速地与其连续的内存布局一样快,即使它仍然可以相当快。