哪种STL容器最适合我的需求?我基本上有一个10个元素宽的容器,在其中不断使用push_back
添加新元素,同时pop_front
弹出最旧的元素(大约一百万次)。
我目前正在使用std::deque
来完成这项任务,但是我想知道是否使用std::list
会更有效率,因为我不需要重新分配空间(或者也许我将std::deque
错认为是std::vector
了?)。还是说有更适合我的需求的容器吗?
P.S.我不需要随机访问。
由于有许多答案,您可能会感到困惑,但总结如下:
使用std::queue
。原因很简单:它是一个FIFO结构。你想要FIFO,就用std::queue
。
这使得你的意图对任何人都清晰明了,甚至对你自己也是如此。std::list
或std::deque
不行。列表可以在任何地方插入和删除,这不是FIFO结构应该做的事情,而deque
可以从任一端添加和删除,这也是FIFO结构不能做的事情。
这就是为什么你应该使用queue
的原因。
现在,你问到性能问题。首先,永远记住这个重要的经验法则:好的代码第一,性能最后。
原因很简单:那些在干净和优雅之前追求性能的人几乎总是最后完成的。他们的代码变成了一堆糊糊的东西,因为他们放弃了所有好的东西,却真正得不到任何东西。
通过首先编写良好、可读的代码,大多数性能问题都会自行解决。如果以后发现性能不足,现在很容易向你的漂亮、干净的代码添加分析器,并找出问题所在。
尽管如此,std::queue
只是个适配器。它提供了安全的接口,但内部使用了不同的容器。您可以选择底层容器,这样可以提供很大程度的灵活性。
那么,应该使用哪种底层容器呢?我们知道std::list
和std::deque
都提供了必要的函数(push_back()
、pop_front()
和front()
),那么我们该如何决定呢?
首先,需要明白分配(和释放)内存通常不是一个快速的过程,因为它涉及到向操作系统请求执行某些任务。每当添加内容时,list
都必须分配内存,并在对象不再使用时进行释放。
而另一方面,deque
是以块为单位进行分配的。它的分配次数比list
少。可以将其想象成一个列表,但每个内存块可以容纳多个节点。(当然,我建议您真正了解它的工作原理。)
因此,仅凭这一点,deque
的性能应该更好,因为它不需要像list
那样频繁地进行内存操作。再加上您正在处理的数据大小是常量,因此它可能不需要在第一次通过数据时分配内存,而list
则需要不断分配和释放内存。
deque
在内存中分配块,所以访问此容器中的元素可能会导致CPU也将其余部分带回。现在,任何进一步访问deque
的操作都将非常快,因为数据已经在缓存中了。deque
应该是更好的选择。这就是为什么在使用queue
时,它是默认容器的原因。但是请记住:先让代码具备清晰的接口,然后再考虑性能。请查看 std::queue
。它包装了一个底层容器类型,而默认容器是 std::deque
。
我需要不断地加入新元素,同时弹出最旧的元素(大约一百万次)。
在计算机中,一百万并不是一个很大的数字。正如其他人建议的那样,首先可以使用 std::queue
作为解决方案。如果不幸它过慢,使用分析器(不要猜测!)确定瓶颈并重新实现使用具有相同接口的不同容器。
为什么不使用 std::queue
呢?它只有 push_back
和 pop_front
。
std::queue
,但要注意两个标准Container
类的性能权衡。默认情况下,std::queue
是在std::deque
上的适配器。通常,在包含大量条目的少量队列的情况下,这将提供良好的性能,这可以说是常见情况。但是,不要对std::deque的实现视而不见。具体来说:“...双端队列通常具有较大的最小内存成本;仅包含一个元素的双端队列必须分配其完整的内部数组(例如,在64位libstdc++上为对象大小的8倍;在64位libc++上为对象大小的16倍或4096字节中的较大值)。”这个案例足够简单,你可以自己编写。以下是一种适用于微控制器情况的方法,其中STL使用会占用太多空间。这是一种很好的方式,可以从中断处理程序传递数据和信号到主循环。
// FIFO with circular buffer
#define fifo_size 4
class Fifo {
uint8_t buff[fifo_size];
int writePtr = 0;
int readPtr = 0;
public:
void put(uint8_t val) {
buff[writePtr%fifo_size] = val;
writePtr++;
}
uint8_t get() {
uint8_t val = NULL;
if(readPtr < writePtr) {
val = buff[readPtr%fifo_size];
readPtr++;
// reset pointers to avoid overflow
if(readPtr > fifo_size) {
writePtr = writePtr%fifo_size;
readPtr = readPtr%fifo_size;
}
}
return val;
}
int count() { return (writePtr - readPtr);}
};
std::deque
不会重新分配内存。它是std::list
和std::vector
的混合体,它分配比std::list
更大的块,但不像std::vector
那样重新分配内存。 - Matt Price