寻找类似 C++ STL 的向量类但使用堆栈存储
Posted
技术标签:
【中文标题】寻找类似 C++ STL 的向量类但使用堆栈存储【英文标题】:Looking for C++ STL-like vector class but using stack storage 【发布时间】:2010-09-26 03:34:22 【问题描述】:在我自己写之前,我会问你们所有人。
我正在寻找一个几乎完全像 STL 向量但将数据存储到堆栈上的数组中的 C++ 类。某种 STL 分配器类也可以工作,但我试图避免任何类型的堆,甚至是静态分配的每线程堆(尽管其中一个是我的第二选择)。堆栈效率更高。
它几乎需要替换当前使用向量的代码。
对于我自己要写的东西,我在想这样的事情:
char buffer[4096];
stack_vector<match_item> matches(buffer, sizeof(buffer));
或者该类可以在内部分配缓冲区空间。然后它看起来像:
stack_vector<match_item, 256> matches;
我在想,如果空间不足,它会抛出 std::bad_alloc,尽管这不应该发生。
更新
使用 Chromium 的 stack_container.h 效果很好!
我自己没想过这样做的原因是我一直忽略了 STL 集合构造函数的分配器对象参数。我曾多次使用模板参数来做静态池,但我从未见过代码或编写过任何实际使用对象参数的代码。我学到了一些新东西。很酷!
代码有点乱,出于某种原因,GCC 强迫我将分配器声明为实际项目,而不是将其构造为向量的分配器参数。它来自这样的东西:
typedef std::pair< const char *, const char * > comp_list_item;
typedef std::vector< comp_list_item > comp_list_type;
comp_list_type match_list;
match_list.reserve(32);
到这里:
static const size_t comp_list_alloc_size = 128;
typedef std::pair< const char *, const char * > comp_list_item;
typedef StackAllocator< comp_list_item, comp_list_alloc_size > comp_list_alloc_type;
typedef std::vector< comp_list_item, comp_list_alloc_type > comp_list_type;
comp_list_alloc_type::Source match_list_buffer;
comp_list_alloc_type match_list_alloc( &match_list_buffer );
comp_list_type match_list( match_list_alloc );
match_list.reserve( comp_list_alloc_size );
每当我宣布一个新的时,我都必须重复这一点。但它就像我想要的那样工作。
我注意到 stack_container.h 定义了一个 StackVector,我尝试使用它。但它没有从 vector 继承或定义相同的方法,因此它不是一个直接替代品。我不想用向量重写所有代码,所以我放弃了。
【问题讨论】:
澄清一下,您想要的东西本质上是一个向量,但作为模板参数的一部分具有固定容量? StackVector 有一种方法可以为您提供实际的 std::vector。只需做 StackVector您不必编写全新的容器类。您可以坚持使用您的 STL 容器,但更改例如 std::vector
的第二个参数,为其提供从堆栈缓冲区分配的自定义分配器。铬作者为此编写了一个分配器:
https://chromium.googlesource.com/chromium/chromium/+/master/base/stack_container.h
它的工作原理是在你说它有多大的地方分配一个缓冲区。您创建容器并调用container.reserve(buffer_size);
。如果溢出该大小,分配器将自动从堆中获取元素(因为它是从std::allocator
派生的,在这种情况下它将只使用标准分配器的功能)。我还没有尝试过,但它看起来像是来自谷歌,所以我认为值得一试。
用法是这样的:
StackVector<int, 128> s;
s->push_back(42); // overloaded operator->
s->push_back(43);
// to get the real std::vector.
StackVector<int, 128>::ContainerType & v = s.container();
std::cout << v[0] << " " << v[1] << std::endl;
【讨论】:
stack_container.h 有点担心:“重要提示:注意确保 stack_buffer_ 是对齐的,因为它用于模拟 T 数组。在声明任何未对齐的类型(如 bool)之前要小心stack_buffer_。”哎呀!堆栈上的自动顺序未定义 IIRC。 是的,这听起来很淘气。更好的是,我们用尽可能大的原始类型围绕它创建一个联合。我现在唯一的想法:联合 unsigned long _a;双长_b; char stack_buffer_[sizeof(T[stack_capacity])]; ;并希望他们能让对齐足够好 否则,我们仍然有 属性 武器和视觉 c++ 提供给我们的任何东西:D 我最近读到了 boost::type_with_alignment看来boost::static_vector 是您正在搜索的内容。来自文档:
static_vector 是向量和数组的混合体:和向量一样,它是一个序列容器,具有连续存储、大小可以改变、静态分配、低开销和固定容量的数组。 static_vector 基于 Adam Wulkiewicz 和 Andrew Hundt 的高性能 varray 类。
static_vector 中的元素数量可以动态变化,直至达到固定容量,因为元素存储在对象本身中,类似于数组。
【讨论】:
【参考方案3】:您可能想要查看的一些选项:
Matthew Wilson(Imperfect C++ 的作者)的 STLSoft 有一个 auto_buffer
模板类,它将默认数组放在堆栈上,但如果它变得大于堆栈分配,则会从堆中获取内存。我喜欢这门课——如果你知道你的容器大小通常会受到一个相当低的限制,那么你就会获得本地堆栈分配数组的速度。但是,对于需要更多内存的极端情况,它仍然可以正常工作。
http://www.stlsoft.org/doc-1.9/classstlsoft_1_1auto__buffer.html
请注意,我自己使用的实现不是 STLSoft 的,而是大量借鉴了它的实现。
“The Lazy Programmer”为使用alloca()
进行存储的容器的实现做了一篇文章。我不喜欢这种技术,但我会让你自己决定是否是你想要的:
http://tlzprgmr.wordpress.com/2008/04/02/c-how-to-create-variable-length-arrays-on-the-stack/
然后是 boost::array
,它没有前两个的动态调整大小行为,但提供了更多 vector
接口,而不仅仅是使用指针作为通过内置数组获得的迭代器(即,您获取begin()
、end()
、size()
等):
http://www.boost.org/doc/libs/1_37_0/doc/html/boost/array.html
【讨论】:
alloca 很棒,但在某些平台上,您可能无法检测到分配失败,直到您收到 SIGSEGV。我相信 Linux 上就是这种情况。【参考方案4】:如果速度很重要,我会看到运行时间
4 ns int[10],堆栈上的固定大小 40 纳秒<vector>
1300 纳秒<stlsoft/containers/pod_vector.hpp>
对于下面的一个愚蠢的测试——只需 2 次推送,v[0] v[1],2 次弹出, 在一个平台上,仅适用于 mac ppc、gcc-4.2 -O3。 (我不知道苹果是否优化了他们的 stl。)
不要接受任何不是你自己伪造的时间。 当然,每种使用模式都是不同的。 尽管如此,因素 > 2 让我感到惊讶。
(如果内存、内存访问是运行时的主要因素, 各种实现中的所有额外内存是什么?)
#include <stlsoft/containers/pod_vector.hpp>
#include <stdio.h>
using namespace std;
int main( int argc, char* argv[] )
// times for 2 push, v[0] v[1], 2 pop, mac g4 ppc gcc-4.2 -O3 --
// Vecint10 v; // stack int[10]: 4 ns
vector<int> v; // 40 ns
// stlsoft::pod_vector<int> v; // 1300 ns
// stlsoft::pod_vector<int, std::allocator<int>, 64> v;
int n = (argv[1] ? atoi( argv[1] ) : 10) * 1000000;
int sum = 0;
while( --n >= 0 )
v.push_back( n );
v.push_back( n );
sum += v[0] + v[1];
v.pop_back();
v.pop_back();
printf( "sum: %d\n", sum );
【讨论】:
【参考方案5】:您可以使用自己的 std::vector 分配器并让它分配基于堆栈的存储块,类似于您的示例。分配器类是模板的第二部分。
编辑:我从来没有尝试过,查看文档进一步让我相信你不能编写自己的分配器。我还在研究。
【讨论】:
撞到了你,因为你靠近右边。我也没有意识到这一点,但你可以让分配器做到这一点。【参考方案6】:tr1::array 部分符合您的描述。它缺少诸如 push___back() 之类的东西,但作为起点可能值得一看。包装它并向“back”添加索引以支持 push_back() 等应该相当容易。
【讨论】:
【参考方案7】:为什么要特别放在stack上?如果你有一个 alloca() 的实现,你可以使用它而不是 malloc() 来构建一个类分配器,但是你使用静态分配数组的想法更好:它在大多数架构上都一样快,而你不需要风险堆栈损坏你搞砸了。
【讨论】:
在堆栈上,因为该程序是多线程和多架构的,我不想搞乱所有不同的方式来获得 TLS 存储。由于堆锁上的线程争用,不在堆上。 在堆上分配每个线程块并像堆栈分配块一样使用它? 需要 TLS,以存储指向“这个线程的”分配块的指针。【参考方案8】:这可能是您使用 Qt 的情况。然后你可能想去QVarLengthArray
(docs)。它基本上位于std::vector
和std::array
之间,静态分配一定数量并在必要时回退到堆分配。
如果我使用它,我更喜欢增强版。
【讨论】:
不错的主意如果我使用的是Qt。我想知道你为什么说“机会是”? Qt 已经足够好了,但是对于 x86、x64、ARM、MIPS 来说,我必须在 MacOS、Windows、Linux(5 种类型)和 BSD(3 种类型)上编译,但有一个巨大的依赖关系。 我的错,我已经在我的脑海中硬连线了“机会是”的不同含义。实际上的意思是“可能是这样”。【参考方案9】:Boost 有这个。它叫small_vector
small_vector 是一个类似矢量的容器,针对以下情况进行了优化 包含很少的元素。它包含一些预先分配的元素 就地,这允许它避免使用动态存储 当实际元素数量低于该数量时分配 预先分配的阈值。 small_vector 的灵感来自 LLVM 的 SmallVector 容器。与 static_vector 不同,small_vector 的容量可以增长 超出初始预分配容量。
small_vector 可转换为 small_vector_base,一种独立于预分配元素的类型 计数,允许不需要在其上模板化的客户端代码 N 论据。 small_vector 继承了所有 vector 的成员函数,所以它 支持所有标准功能,如 emplacement、有状态分配器、 等等
【讨论】:
boost::static_vector
对于 OP 来说可能更简单和语义化,因为他们说 “我试图避免任何类型的堆”,所以他们不需要或希望boost::small_vector
的能力超过其堆栈计数并从堆开始分配。我没有对此进行调查,但static_vector
可能会因为不必考虑这一点而获得一些效率。【参考方案10】:
如果您想在堆栈上分配但不想在编译时预先定义最大大小,您可以使用StackVector,这是一个可以像这样使用的小实现:
new_stack_vector(Type, name, size)
其中Type
是向量中元素的类型,name
是向量的变量名,size
是向量中允许的最大元素数。
size
可以是变量,不需要是编译时常量! :D
例子:
new_stack_vector(int, vec, 100); //like vector<int> vec; vec.reserve(100); but on the stack :)
vec.push_back(10); //added "10" as the first item in the vector
...仅此而已!
免责声明:一般情况下,切勿在堆栈上使用非常大的数组大小。就像你不应该使用int var[9999999]
一样,你也不应该使用new_stack_vector(int, vec, 9999999)
!负责任地使用。
【讨论】:
以上是关于寻找类似 C++ STL 的向量类但使用堆栈存储的主要内容,如果未能解决你的问题,请参考以下文章
在 C++ 中为我自己的自定义向量模板类实现迭代器(类似于 STL)[重复]