寻找类似 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::ContainerType & v = stack_vector.container();为拿到它,为实现它。 v 那么是一个实际的 std::vector。还可以更好地使用我在回答的评论中解释的联合技巧。 否则,如果您经常使用该向量,您可能会遇到严重的性能问题,并且在某些平台(许多非 x86)上它甚至可能崩溃。 是的,我还在 Itanium 系统上构建以捕获此类错误。我已经修改了 stack_container.h 中的代码,但它并没有按原样编译。 【参考方案1】:

您不必编写全新的容器类。您可以坚持使用您的 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::type,它可以解决你的确切问题。 Chromium 的 stack_container.h 的链接已失效。这是一个更新的:src.chromium.org/viewvc/chrome/trunk/src/base/containers/…【参考方案2】:

看来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 纳秒&lt;vector&gt; 1300 纳秒&lt;stlsoft/containers/pod_vector.hpp&gt;

对于下面的一个愚蠢的测试——只需 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::vectorstd::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++ 堆栈分配对象,显式析构函数调用

在 C++ 中为我自己的自定义向量模板类实现迭代器(类似于 STL)[重复]

C++:问题向量 STL

C++ STL 问题:分配器

如何使 c++ 代码既可用于 Qt 项目(QTL 风格)也可用于 C++ 项目(STL 风格)

对象向量是不是在 C++ 中的堆或堆栈上分配?