使用vector <char>作为缓冲区而不在resize()上初始化它

Mar*_*tin 23 c++ boost c++11

我想vector<char>用作缓冲区.该接口非常适合我的需求,但由于内存已初始化,因此在将其大小调整到超出当前大小时会有性能损失.我不需要初始化,因为在任何情况下,某些第三方C函数都会覆盖数据.有没有办法或特定的分配器来避免初始化步骤?请注意,我想使用resize(),没有其他的技巧,比如reserve()capacity(),因为我需要size()总是代表我的"缓冲"在任何时刻的有意义的大小,同时capacity()可能会比以后它的尺寸更大resize(),所以,再一次,我可以不依赖capacity()作为我的申请的重要信息.此外,矢量的(新)大小事先不知道,所以我不能使用std::array.如果vector不能以这种方式配置,我想知道我可以使用哪种容器或分配器而不是vector<char, std::alloc>.唯一的要求是vector的替代方案最多必须基于STL或Boost.我可以访问C++ 11.

Max*_*kin 26

已知的问题是,即使显式也无法关闭初始化std::vector.

人们通常会实现自己的pod_vector<>,不会对元素进行任何初始化.

另一种方法是创建一个与char布局兼容的类型,其构造函数不执行任何操作:

struct NoInitChar
{
    char value;
    NoInitChar() noexcept {
        // do nothing
        static_assert(sizeof *this == sizeof value, "invalid size");
        static_assert(__alignof *this == __alignof value, "invalid alignment");
    }
};

int main() {
    std::vector<NoInitChar> v;
    v.resize(10); // calls NoInitChar() which does not initialize

    // Look ma, no reinterpret_cast<>!
    char* beg = &v.front().value;
    char* end = beg + v.size();
}
Run Code Online (Sandbox Code Playgroud)

  • @Martin标准要求a)聚合的对齐是其成员之一与最大对齐要求的对齐的倍数,b)结构的大小是其对齐的倍数.换句话说,该标准保证`NoInitChar`与`char`具有相同的大小和对齐方式.那些静态断言主要是可编译的文档. (3认同)
  • @MaximYegorushkin真的吗?听起来,它只是保证了它的大小和对齐方式是“ char”大小和对齐方式的“互斥量”(无论编译器通过添加不必要的填充而获得什么好处,但是标准至少允许它)。因此,即使在任何合理的平台上,这些主张在标准上也不是正确的。但是有趣的答案是+1。 (2认同)

Joe*_*oeG 19

标准库中没有任何内容符合您的要求,也没有任何我知道的提升.

我能想到三个合理的选择:

  • std::vector现在坚持,在代码中留下评论并回到它,如果这会导致您的应用程序的瓶颈.
  • 使用带有空construct/ destroy方法的自定义分配器- 并希望您的优化器足够聪明,可以删除对它们的任何调用.
  • 围绕动态分配的数组创建一个包装器,仅实现您需要的最小功能.

  • @WhozCraig:我会丢弃任何无法删除对空函数的调用的编译器,这是一个非常简单的优化.我认为自定义分配器是最干净,最简单的方法. (5认同)
  • @MaximYegorushkin,对于普通类型,如果使用`std :: allocator`,它使用`_Construct`,因为结果是"好像"每个元素都已被`std :: allocator <T> :: construct`初始化,但是不使用优化的自定义分配器.请参阅`<bits/stl_uninitialized.h>`和`__uninitialized_fill_n_a`函数,它在分配器类型上重载并调度到`__uninitialized_fill_n`或`allocator_traits <A> :: construct` (3认同)
  • +1第二项是*大*希望,但如果优化器相当聪明,它可能会看到亮点.第三个显然授予了最多的控制权,但代价是要维护更多的代码(但它真的会比自定义分配器派生更糟糕吗?可能不会.) (2认同)

Pav*_*l P 9

作为适用于不同 pod 类型向量的替代解决方案:

template<typename V>
void resize(V& v, size_t newSize)
{
    struct vt { typename V::value_type v; vt() {}};
    static_assert(sizeof(vt[10]) == sizeof(typename V::value_type[10]), "alignment error");
    typedef std::vector<vt, typename std::allocator_traits<typename V::allocator_type>::template rebind_alloc<vt>> V2;
    reinterpret_cast<V2&>(v).resize(newSize);
}
Run Code Online (Sandbox Code Playgroud)

然后你可以:

std::vector<char> v;
resize(v, 1000); // instead of v.resize(1000);
Run Code Online (Sandbox Code Playgroud)

这很可能是UB,尽管在我更关心性能的情况下它对我来说工作正常。由 clang 生成的生成程序集的差异:

test():
        push    rbx
        mov     edi, 1000
        call    operator new(unsigned long)
        mov     rbx, rax
        mov     edx, 1000
        mov     rdi, rax
        xor     esi, esi
        call    memset
        mov     rdi, rbx
        pop     rbx
        jmp     operator delete(void*)

test_noinit():
        push    rax
        mov     edi, 1000
        call    operator new(unsigned long)
        mov     rdi, rax
        pop     rax
        jmp     operator delete(void*)
Run Code Online (Sandbox Code Playgroud)

  • 由于明显的语言疏忽而不得不使用可疑的未定义行为,欢呼三声。我谦虚地建议将该函数重命名为“resize_hack”或类似的名称,以认识到这一事实。 (2认同)