繁体   English   中英

如何避免std :: vector <>初始化其所有元素?

[英]How can I avoid std::vector<> to initialize all its elements?

编辑:我编辑了问题及其标题更精确。

考虑以下源代码:

#include <vector>
struct xyz {
    xyz() { } // empty constructor, but the compiler doesn't care
    xyz(const xyz& o): v(o.v) { } 
    xyz& operator=(const xyz& o) { v=o.v; return *this; }
    int v; // <will be initialized to int(), which means 0
};

std::vector<xyz> test() {
    return std::vector<xyz>(1024); // will do a memset() :-(
}

...我怎么能避免vector <>分配的内存用它的第一个元素的副本进行初始化,这是一个O(n)操作我宁愿为了速度而跳过,因为我的默认构造函数什么也没做?

如果不存在通用的解决方案,那么g ++特定的解决方案就可以做到(但我找不到任何属性来执行此操作)。

编辑 :生成的代码如下(命令行:arm-elf-g ++ - 4.5 -O3 -S -fno-verbose-asm -o-test.cpp | arm-elf-c ++ filt | grep -vE'^ [[: space:]] + [。@]。* $')

test():
    mov r3, #0
    stmfd   sp!, {r4, lr}
    mov r4, r0
    str r3, [r0, #0]
    str r3, [r0, #4]
    str r3, [r0, #8]
    mov r0, #4096
    bl  operator new(unsigned long)
    add r1, r0, #4096
    add r2, r0, #4080
    str r0, [r4, #0]
    stmib   r4, {r0, r1}
    add r2, r2, #12
    b       .L4          @
.L8:                     @
    add     r0, r0, #4   @
.L4:                     @
    cmp     r0, #0       @  fill the memory
    movne   r3, #0       @
    strne   r3, [r0, #0] @
    cmp     r0, r2       @
    bne     .L8          @
    str r1, [r4, #4]
    mov r0, r4
    ldmfd   sp!, {r4, pc}

编辑:为了完整性,这里是x86_64的程序集:

.globl test()
test():
LFB450:
    pushq   %rbp
LCFI0:
    movq    %rsp, %rbp
LCFI1:
    pushq   %rbx
LCFI2:
    movq    %rdi, %rbx
    subq    $8, %rsp
LCFI3:
    movq    $0, (%rdi)
    movq    $0, 8(%rdi)
    movq    $0, 16(%rdi)
    movl    $4096, %edi
    call    operator new(unsigned long)
    leaq    4096(%rax), %rcx
    movq    %rax, (%rbx)
    movq    %rax, 8(%rbx)
    leaq    4092(%rax), %rdx
    movq    %rcx, 16(%rbx)
    jmp     L4          @
L8:                     @
    addq    $4, %rax    @
L4:                     @
    testq   %rax, %rax  @ memory-filling loop
    je      L2          @
    movl    $0, (%rax)  @
L2:                     @
    cmpq    %rdx, %rax  @
    jne     L8          @
    movq    %rcx, 8(%rbx)
    movq    %rbx, %rax
    addq    $8, %rsp
    popq    %rbx
    leave
LCFI4:
    ret
LFE450:
EH_frame1:
LSCIE1:
LECIE1:
LSFDE1:
LASFDE1:
LEFDE1:

编辑:我认为当你想避免不必要的初始化时,结论是不使用std::vector<> 我最终展开了我自己的模板化容器,它表现得更好(并且具有适用于neon和armv7的专用版本)。

分配的元素的初始化由Allocator模板参数控制,如果您需要自定义,则自定义它。 但请记住,在脏黑客的情况下,这很容易结束,因此请谨慎使用。 例如,这是一个非常脏的解决方案。 它将避免初始化,但它很可能会在性能上更差,但为了演示的缘故(因为人们已经说过这是不可能的!......不可能不是C ++程序员的词汇!):

template <typename T>
class switch_init_allocator : public std::allocator< T > {
  private:
    bool* should_init;
  public:
    template <typename U>
    struct rebind {
      typedef switch_init_allocator<U> other;
    };

    //provide the required no-throw constructors / destructors:
    switch_init_allocator(bool* aShouldInit = NULL) throw() : std::allocator<T>(), should_init(aShouldInit) { };
    switch_init_allocator(const switch_init_allocator<T>& rhs) throw() : std::allocator<T>(rhs), should_init(rhs.should_init) { };
    template <typename U>
    switch_init_allocator(const switch_init_allocator<U>& rhs, bool* aShouldInit = NULL) throw() : std::allocator<T>(rhs), should_init(aShouldInit) { };
    ~switch_init_allocator() throw() { };

    //import the required typedefs:
    typedef typename std::allocator<T>::value_type value_type;
    typedef typename std::allocator<T>::pointer pointer;
    typedef typename std::allocator<T>::reference reference;
    typedef typename std::allocator<T>::const_pointer const_pointer;
    typedef typename std::allocator<T>::const_reference const_reference;
    typedef typename std::allocator<T>::size_type size_type;
    typedef typename std::allocator<T>::difference_type difference_type;

    //redefine the construct function (hiding the base-class version):
    void construct( pointer p, const_reference cr) {
      if((should_init) && (*should_init))
        new ((void*)p) T ( cr );
      //else, do nothing.
    };
};

template <typename T>
class my_vector : public std::vector<T, switch_init_allocator<T> > {
  public:
    typedef std::vector<T, switch_init_allocator<T> > base_type;
    typedef switch_init_allocator<T> allocator_type;
    typedef std::vector<T, allocator_type > vector_type;
    typedef typename base_type::size_type size_type;
  private:
    bool switch_flag; //the order here is very important!!
    vector_type vec;
  public:  
    my_vector(size_type aCount) : switch_flag(false), vec(aCount, allocator_type(&switch_flag)) { };
    //... and the rest of this wrapper class...
    vector_type& get_vector() { return vec; };
    const vector_type& get_vector() const { return vec; };
    void set_switch(bool value) { switch_flag = value; };
};

class xyz{};

int main(){
  my_vector<xyz> v(1024); //this won't initialize the memory at all.
  v.set_switch(true); //set back to true to turn initialization back on (needed for resizing and such)
}

当然,上面的内容很笨拙而且不推荐,当然也没有比实际让内存充满第一个元素的副本更好(特别是因为使用这个标志检查会妨碍每个元素构造) 。 但是,当想要优化STL容器中元素的分配和初始化时,这是一个探索的途径,所以我想展示它。 关键是,你可以注入代码的唯一地方是阻止std :: vector容器调用copy-constructor来初始化你的元素,这是在vector的allocator对象的construct函数中。

此外,您可以取消“切换”并简单地执行“no-init-allocator”,但是,您还可以关闭在调整大小期间复制数据所需的复制构造(这将使此向量类更多不太有用)。

这是vector一个奇怪的角落。 问题在于你的元素是值初始化的......而是第一个原型元素中的随机内容被复制到向量中的所有其他元素。 (此行为随C ++ 11而改变,该值初始化每个元素)。

这是(/ was)完成的原因:考虑一些引用计数对象...如果构造一个vector要求初始化为这样一个对象的1000个元素,你显然想要一个引用计数为1000的对象,而不是1000个独立的“克隆”。 我说“显然”是因为首先计算了对象引用意味着非常需要。

无论如何,你几乎没有运气。 实际上, vector确保所有元素都是相同的,即使它同步的内容恰好是未初始化的垃圾。


在非标准g ++特定的快乐黑客的领域,我们可以利用vector接口中的任何公共模板化成员函数作为后门来简单地通过专门化某些新类型的模板来更改私有成员数据。

警告 :不仅仅是为了这个“解决方案”,而是为了避免默认构造的整个工作... 不要对具有重要不变量的类型执行此操作 - 您打破封装并且可以轻松地使用vector本身或您尝试调用operator=()某些操作operator=() ,复制构造函数和/或析构函数,其中*this / left-和/或右侧参数不遵守这些不变量。 例如,避免使用您希望为NULL的指针的值类型或有效对象,引用计数器,资源句柄等。

#include <iostream>
#include <vector>

struct Uninitialised_Resize
{
    explicit Uninitialised_Resize(int n) : n_(n) { }
    explicit Uninitialised_Resize() { }
    int n_;
};

namespace std
{
    template <>
    template <>
    void vector<int>::assign(Uninitialised_Resize ur, Uninitialised_Resize)
    {
        this->_M_impl._M_finish = this->_M_impl._M_start + ur.n_;

        // note: a simpler alternative (doesn't need "n_") is to set...
        //   this->_M_impl._M_finish = this->_M_impl._M_end_of_storage;
        // ...which means size() will become capacity(), which may be more
        // you reserved() (due to rounding; good) or have data for
        // (bad if you have to track in-use elements elsewhere,
        //  which makes the situation equivalent to just reserve()),
        // but if you can somehow use the extra elements then all's good.
    }
}

int main()
{
    {
        // try to get some non-0 values on heap ready for recycling...
        std::vector<int> x(10000);
        for (int i = 0; i < x.size(); ++i)
            x[i] = i;
    }

    std::vector<int> x;
    x.reserve(10000);
    for (int i = 1; i < x.capacity(); ++i)
        if (x[0] != x[i])
        {
            std::cout << "lucky\n";
            break;
        }
    x.assign(Uninitialised_Resize(1000), Uninitialised_Resize());

    for (int i = 1; i < x.size(); ++i)
        if (x[0] != x[i])
        {
            std::cout << "success [0] " << x[0] << " != [" << i << "] "
                << x[i] << '\n';
            break;
        }
}

我的输出:

lucky
success [0] 0 != [1] 1

这表明新的vector被重新分配到第一个向量在超出范围时释放的堆,并显示值不会被赋值所破坏。 当然,如果不仔细检查vector源,就无法知道其他一些重要的类不变量是否已经失效,私人成员的确切名称/导入可能随时变化....

您将所有基元包装在结构中:

struct IntStruct
{
    IntStruct();

    int myInt;
}

将IntStruct()定义为空构造函数。 因此,您将v声明为IntStruct v; 因此,当xyzsvector全部为值初始化时,它们所做的只是值初始化v,这是一个无操作。

编辑:我误解了这个问题。 如果你有一个原始类型的vector ,你应该这样做,因为vector被定义为在通过resize()方法创建元素时进行值初始化。 在构造时,结构不需要对其成员进行值初始化,尽管这些“未初始化”的值仍然可以通过其他东西设置为0 - 嘿,它们可以是任何东西。

你无法避免std :: vector的元素初始化。

因此,我使用std :: vector派生类。 resize()在此示例中实现。 您也必须实现构造函数。

虽然这不是标准的C ++而是编译器实现:-(

#include <vector>

template<typename _Tp, typename _Alloc = std::allocator<_Tp>>
class uvector : public std::vector<_Tp, _Alloc>
{
    typedef std::vector<_Tp, _Alloc> parent;
    using parent::_M_impl;

public:
    using parent::capacity;
    using parent::reserve;
    using parent::size;
    using typename parent::size_type;

    void resize(size_type sz)
    {
        if (sz <= size())
            parent::resize(sz);
        else
        {
            if (sz > capacity()) reserve(sz);
            _M_impl._M_finish = _M_impl._M_start + sz;
        }
    }
};

我没有看到内存初始化。 默认的int()构造函数什么都不做,就像在C中一样。

程序:

#include <iostream>
#include <vector>

struct xyz {
    xyz() {}
    xyz(const xyz& o): v(o.v) {} 
    xyz& operator=(const xyz& o) { v=o.v; return *this; }
    int v;
};

std::vector<xyz> test() {
    return std::vector<xyz>(1024);
}

int main()
{
    std::vector<xyz> foo = test();
    for(int i = 0; i < 10; ++i)
    {
        std::cout << i << ": " << foo[i].v << std::endl;
    }
    return 0;
}

输出:

$ g++ -o foo foo.cc
$ ./foo 
0: 1606418432
1: 1606418432
2: 1606418432
3: 1606418432
4: 1606418432
5: 1606418432
6: 1606418432
7: 1606418432
8: 1606418432
9: 1606418432

编辑:

如果您只是尝试将向量初始化为一些重要的东西,并且不想浪费时间默认构造其内容,您可能想尝试创建自定义迭代器并将其传递给向量的构造函数。

修改示例:

#include <iostream>
#include <vector>
#include <iterator>

struct xyz {
    xyz() {}
    xyz(int init): v(init) {}
    xyz(const xyz& o): v(o.v) {} 
    xyz& operator=(const xyz& o) { v=o.v; return *this; }
    int v;
};

class XYZInitIterator: public std::iterator<std::input_iterator_tag, xyz>
{
public:
                        XYZInitIterator(int init): count(init) {}
                        XYZInitIterator(const XYZInitIterator& iter)
                        : count(iter.count) {}
    XYZInitIterator&    operator=(const XYZInitIterator& iter)
                        { count = iter.count; return *this; }
    value_type          operator*() const { return xyz(count); }
    bool                operator==(const XYZInitIterator& other) const 
                        { return count == other.count; }
    bool                operator!=(const XYZInitIterator& other) const 
                        { return count != other.count; }
    value_type          operator++() { return xyz(++count); }
    value_type          operator++(int) { return xyz(count++); }
private:
    int count;
};

std::vector<xyz> test() {
    XYZInitIterator start(0), end(1024);
    return std::vector<xyz>(start, end);
}

int main()
{
    std::vector<xyz> foo = test();
    for(int i = 0; i < 10; ++i)
    {
        std::cout << std::dec << i << ": " << std::hex << foo[i].v << std::endl;
    }
    return 0;
}

输出:

$ g++ -o foo foo.cc
$ ./foo 
0: 0
1: 1
2: 2
3: 3
4: 4
5: 5
6: 6
7: 7
8: 8
9: 9

作为参考,以下代码导致g ++中的最佳组装: 我不是说我会使用它而我不鼓励你。 它不适合C ++! 这是一个非常非常肮脏的黑客! 我想它甚至可能依赖于g ++版本,所以,真的,不要使用它。 如果我看到它用在某处,我会呕吐。

#include <vector>

template<typename T>
static T create_uninitialized(size_t size, size_t capacity) {
    T v;
#if defined(__GNUC__)
    // Don't say it. I know -_-;
    // Oddly, _M_impl is public in _Vector_base !?
    typedef typename T::value_type     value_type;
    typedef typename T::allocator_type allocator_type;
    typedef std::_Vector_base<value_type, allocator_type> base_type;
    base_type& xb(reinterpret_cast<base_type&>(v));
    value_type* p(new value_type[capacity]);
#if !defined(__EXCEPTIONS)
    size=p?size:0;         // size=0 if p is null
    capacity=p?capacity:0; // capacity=0 if p is null
#endif
    capacity=std::max(size, capacity); // ensure size<=capacity
    xb._M_impl._M_start = p;
    xb._M_impl._M_finish = p+size;
    xb._M_impl._M_end_of_storage = p+capacity;
#else
    // Fallback, for the other compilers
    capacity=std::max(size, capacity);
    v.reserve(capacity);
    v.resize(size);
#endif
    return v;
}

struct xyz {
    // empty default constructor
    xyz() { }
    xyz(const xyz& o): v(o.v) { }
    xyz& operator=(const xyz& o) { v=o.v; return *this; }
    int v;
    typedef std::vector<xyz> vector;
};

// test functions for assembly dump
extern xyz::vector xyz_create() {
    // Create an uninitialized vector of 12 elements, with
    // a capacity to hold 256 elements.
    return create_uninitialized<xyz::vector>(12,256);
}

extern void xyz_fill(xyz::vector& x) {
    // Assign some values for testing
    for (int i(0); i<x.size(); ++i) x[i].v = i;
}

// test
#include <iostream>
int main() {
    xyz::vector x(xyz_create());
    xyz_fill(x);
    // Dump the vector
    for (int i(0); i<x.size(); ++i) std::cerr << x[i].v << "\n";
    return 0;
}

编辑:实现_Vector_impl是公开的,这简化了事情。

编辑:这是为xyz_create()生成的ARM程序集,使用-fno-exceptions(使用c ++ filt进行解码)编译并且没有任何内存初始化循环:

xyz_create():
    mov r3, #0
    stmfd   sp!, {r4, lr}
    mov r4, r0
    str r3, [r0, #0]
    str r3, [r0, #4]
    str r3, [r0, #8]
    mov r0, #1024
    bl  operator new[](unsigned long)(PLT)
    cmp r0, #0
    moveq   r3, r0
    movne   r3, #1024
    moveq   r2, r0
    movne   r2, #48
    add r2, r0, r2
    add r3, r0, r3
    stmia   r4, {r0, r2, r3}    @ phole stm
    mov r0, r4
    ldmfd   sp!, {r4, pc}

..和x86_64这里:

xyz_create():
    pushq   %rbp
    movq    %rsp, %rbp
    pushq   %rbx
    movq    %rdi, %rbx
    subq    $8, %rsp
    movq    $0, (%rdi)
    movq    $0, 8(%rdi)
    movq    $0, 16(%rdi)
    movl    $1024, %edi
    call    operator new[](unsigned long)
    cmpq    $1, %rax
    movq    %rax, (%rbx)
    sbbq    %rdx, %rdx
    notq    %rdx
    andl    $1024, %edx
    cmpq    $1, %rax
    sbbq    %rcx, %rcx
    leaq    (%rax,%rdx), %rdx
    notq    %rcx
    andl    $48, %ecx
    movq    %rdx, 16(%rbx)
    leaq    (%rax,%rcx), %rcx
    movq    %rbx, %rax
    movq    %rcx, 8(%rbx)
    addq    $8, %rsp
    popq    %rbx
    leave
    ret

我也很好奇。 你是否只想将内存随机初始化?

向量元素存储在连续的存储器位置中,因此随机初始化是可能的。

如果您想要一个仅保留内存但没有初始化元素的向量,请使用reserve而不是构造函数:

std::vector<xyz> v;
v.reserve(1024);
assert(v.capacity() >= 1024);
assert(v.size() == 0);

使用此方法声明struct的方式,没有机制来默认初始化结构的int成员,因此您获得默认的C行为,这是一个不确定的初始化。 为了使用默认初始化值初始化int成员变量,您必须将其添加到结构构造函数的初始化列表中。 例如,

struct xyz {
    xyz(): v() { } //initialization list sets the value of int v to 0
    int v;
};

其中,作为

struct xyz {
    xyz(): { } //no initialization list, therefore 'v' remains uninitialized
    int v;
};

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM