简体   繁体   English

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

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

EDIT: I edited both the question and its title to be more precise. 编辑:我编辑了问题及其标题更精确。

Considering the following source code: 考虑以下源代码:

#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() :-(
}

...how can I avoid the memory allocated by the vector<> to be initialized with copies of its first element, which is a O(n) operation I'd rather skip for the sake of speed, since my default constructor does nothing ? ...我怎么能避免vector <>分配的内存用它的第一个元素的副本进行初始化,这是一个O(n)操作我宁愿为了速度而跳过,因为我的默认构造函数什么也没做?

A g++ specific solution will do, if no generic one exists (but I couldn't find any attribute to do that). 如果不存在通用的解决方案,那么g ++特定的解决方案就可以做到(但我找不到任何属性来执行此操作)。

EDIT : generated code follows (command line: arm-elf-g++-4.5 -O3 -S -fno-verbose-asm -o - test.cpp | arm-elf-c++filt | grep -vE '^[[:space:]]+[.@].*$' ) 编辑 :生成的代码如下(命令行: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}

EDIT: For the sake of completeness, here is the assembly for x86_64: 编辑:为了完整性,这里是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:

EDIT: I think the conclusion is to not use std::vector<> when you want to avoid unneeded initialization. 编辑:我认为当你想避免不必要的初始化时,结论是不使用std::vector<> I ended up unrolling my own templated container, which performs better (and has specialized versions for neon and armv7). 我最终展开了我自己的模板化容器,它表现得更好(并且具有适用于neon和armv7的专用版本)。

The initialization of the elements allocated is controlled by the Allocator template argument, if you need it customized, customize it. 分配的元素的初始化由Allocator模板参数控制,如果您需要自定义,则自定义它。 But remember that this can get easily wind-up in the realm of dirty hacking, so use with caution. 但请记住,在脏黑客的情况下,这很容易结束,因此请谨慎使用。 For instance, here is a pretty dirty solution. 例如,这是一个非常脏的解决方案。 It will avoid the initialization, but it most probably will be worse in performance, but for demonstration's sake (as people have said this is impossible!... impossible is not in a C++ programmer's vocabulary!): 它将避免初始化,但它很可能会在性能上更差,但为了演示的缘故(因为人们已经说过这是不可能的!......不可能不是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)
}

Of course, the above is awkward and not recommended, and certainly won't be any better than actually letting the memory get filled with copies of the first element (especially since the use of this flag-checking will impede on each element-construction). 当然,上面的内容很笨拙而且不推荐,当然也没有比实际让内存充满第一个元素的副本更好(特别是因为使用这个标志检查会妨碍每个元素构造) 。 But it is an avenue to explore when looking to optimize the allocation and initialization of elements in an STL container, so I wanted to show it. 但是,当想要优化STL容器中元素的分配和初始化时,这是一个探索的途径,所以我想展示它。 The point is that the only place you can inject code that will stop the std::vector container from calling the copy-constructor to initialize your elements is in the construct function of the vector's allocator object. 关键是,你可以注入代码的唯一地方是阻止std :: vector容器调用copy-constructor来初始化你的元素,这是在vector的allocator对象的construct函数中。

Also, you could do away with the "switch" and simply do a "no-init-allocator", but then, you also turn off copy-construction which is needed to copy the data during resizing (which would make this vector class much less useful). 此外,您可以取消“切换”并简单地执行“no-init-allocator”,但是,您还可以关闭在调整大小期间复制数据所需的复制构造(这将使此向量类更多不太有用)。

This is a strange corner of the vector . 这是vector一个奇怪的角落。 The problem is not that your element is being value initialised... it's that the random content in the first prototypal element is copied to all the other elements in the vector. 问题在于你的元素是值初始化的......而是第一个原型元素中的随机内容被复制到向量中的所有其他元素。 (This behaviour changed with C++11, which value initialises each element). (此行为随C ++ 11而改变,该值初始化每个元素)。

This is(/was) done for a good reason: consider some reference counted object... if you construct a vector asking for 1000 elements initialised to such an object, you obviously want one object with a reference count of 1000, rather than having 1000 independent "clones". 这是(/ was)完成的原因:考虑一些引用计数对象...如果构造一个vector要求初始化为这样一个对象的1000个元素,你显然想要一个引用计数为1000的对象,而不是1000个独立的“克隆”。 I say "obviously" because having made the object reference counted in the first place implies that's highly desirable. 我说“显然”是因为首先计算了对象引用意味着非常需要。

Anyway, you're almost out of luck. 无论如何,你几乎没有运气。 Effectively, the vector is ensuring that all the elements are the same, even if the content it's syncing to happens to be uninitialised garbage. 实际上, vector确保所有元素都是相同的,即使它同步的内容恰好是未初始化的垃圾。


In the land of non-Standard g++-specific happy-hacking, we can exploit any public templated member function in the vector interface as a backdoor to change private member data simply by specialising the template for some new type. 在非标准g ++特定的快乐黑客的领域,我们可以利用vector接口中的任何公共模板化成员函数作为后门来简单地通过专门化某些新类型的模板来更改私有成员数据。

WARNING : not just for this "solution" but for this whole effort to avoid default construction... don't do this for types with important invariants - you break encapsulation and can easily have vector itself or some operation you attempt invoke operator=() , copy-constructors and/or destructors where *this /left- and/or right-hand-side arguments don't honour those invariants. 警告 :不仅仅是为了这个“解决方案”,而是为了避免默认构造的整个工作... 不要对具有重要不变量的类型执行此操作 - 您打破封装并且可以轻松地使用vector本身或您尝试调用operator=()某些操作operator=() ,复制构造函数和/或析构函数,其中*this / left-和/或右侧参数不遵守这些不变量。 For example, avoid value-types with pointers that you expect to be NULL or to valid objects, reference counters, resource handles etc.. 例如,避免使用您希望为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;
        }
}

My output: 我的输出:

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

This suggests the new vector was reallocated the heap that the first vector released when it went out of scope, and shows the values aren't clobbered by the assign. 这表明新的vector被重新分配到第一个向量在超出范围时释放的堆,并显示值不会被赋值所破坏。 Of course, there's no way to know if some other important class invariants have been invalidated without inspecting the vector sources very carefully, and the exact names/import of private members can vary at any time.... 当然,如果不仔细检查vector源,就无法知道其他一些重要的类不变量是否已经失效,私人成员的确切名称/导入可能随时变化....

You wrap all your primitives in a struct: 您将所有基元包装在结构中:

struct IntStruct
{
    IntStruct();

    int myInt;
}

with IntStruct() defined as an empty constructor. 将IntStruct()定义为空构造函数。 Thus you declare v as IntStruct v; 因此,您将v声明为IntStruct v; so when a vector of xyzs all value-initialize, all they do is value-initialize v which is a no-op. 因此,当xyzsvector全部为值初始化时,它们所做的只是值初始化v,这是一个无操作。

EDIT: I misread the question. 编辑:我误解了这个问题。 This is what you should do if you have a vector of primitive types, because vector is defined to value-initialize upon creating elements through the resize() method. 如果你有一个原始类型的vector ,你应该这样做,因为vector被定义为在通过resize()方法创建元素时进行值初始化。 Structs are not required to value-initialize their members upon construction, though these "uninitialized" values can still be set to 0 by something else -- hey, they could be anything. 在构造时,结构不需要对其成员进行值初始化,尽管这些“未初始化”的值仍然可以通过其他东西设置为0 - 嘿,它们可以是任何东西。

You cannot avoid std::vector's elements initialization. 你无法避免std :: vector的元素初始化。

I use a std::vector derived class for that reason. 因此,我使用std :: vector派生类。 resize() is implemented in this example. resize()在此示例中实现。 You must implement constructors too. 您也必须实现构造函数。

Although this is not standard C++ but compiler implementation :-( 虽然这不是标准的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;
        }
    }
};

I'm not seeing the memory initialized. 我没有看到内存初始化。 The default int() constructor does nothing, just like in C. 默认的int()构造函数什么都不做,就像在C中一样。

Program: 程序:

#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;
}

Output: 输出:

$ 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

EDIT: 编辑:

If you're just trying to initialize the vector to some nontrivial thing, and don't want to waste time default-constructing its contents, you might want to try creating a custom iterator and passing it to the vector's constructor. 如果您只是尝试将向量初始化为一些重要的东西,并且不想浪费时间默认构造其内容,您可能想尝试创建自定义迭代器并将其传递给向量的构造函数。

Modified example: 修改示例:

#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;
}

Output: 输出:

$ 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

For reference, the following code leads to optimal assembly in g++: I am not saying I will ever use it and I don't encourage you to. 作为参考,以下代码导致g ++中的最佳组装: 我不是说我会使用它而我不鼓励你。 It is not proper C++! 它不适合C ++! It's a very, very dirty hack! 这是一个非常非常肮脏的黑客! I guess it might even depend on g++ version, so, really, do not use it. 我想它甚至可能依赖于g ++版本,所以,真的,不要使用它。 I would puke if I saw it used somewhere. 如果我看到它用在某处,我会呕吐。

#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;
}

EDIT: realized _Vector_impl was public, which simplify things. 编辑:实现_Vector_impl是公开的,这简化了事情。

EDIT: here is the generated ARM assembly for xyz_create(), compiled with -fno-exceptions (demangled using c++filt) and without any memory-initializing loop: 编辑:这是为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}

..and here for x86_64: ..和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

I am also curious. 我也很好奇。 Do you just want the memory random initialized? 你是否只想将内存随机初始化?

Vector elements are stored in consecutive memory locations so random initialization is a possibility. 向量元素存储在连续的存储器位置中,因此随机初始化是可能的。

If you want a vector with only memory reserved, but no initialized elements, use reserve instead of the constructor: 如果您想要一个仅保留内存但没有初始化元素的向量,请使用reserve而不是构造函数:

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

With the way your struct is declared at the moment, there is no mechanism to default initialize the int member of your struct, therefore you get the default C behavior which is an indeterminate initialization. 使用此方法声明struct的方式,没有机制来默认初始化结构的int成员,因此您获得默认的C行为,这是一个不确定的初始化。 In order to initialize the int member variable with a default initialization value, you would have to add it to the initialization list of the structure's constructor. 为了使用默认初始化值初始化int成员变量,您必须将其添加到结构构造函数的初始化列表中。 For example, 例如,

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

Where-as 其中,作为

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

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

相关问题 我可以通过元素的完美转发来列表初始化 std::vector 吗? - Can I list-initialize std::vector with perfect forwarding of the elements? 如何初始化std :: map项的std :: vector? - How can I initialize an std::vector of std::map items? C++:为 std::vector 分配内存,然后并行初始化其元素 - C++: Allocate memory for an std::vector then initialize its elements in parallel 我可以使用std :: vector用其他默认参数初始化元素吗? - Can I initialize elements with an additional default-parameter using std::vector? std :: vector是否改变了它的地址?怎么避免 - Does std::vector change its address? How to avoid 我可以从向量数组初始化一个 std::tuple 吗? - can I initialize an std::tuple from a vector array? 由于运算符重载,我可以使用&#39;=&#39;初始化std :: vector吗? - Can I initialize a std::vector with '=' thanks to the operator overloading? 为什么我不能用list-initialization初始化std :: vector - Why can't I initialize std::vector with list-initialization 为什么我不能用左值初始化这个 std::vector? - Why can't I initialize this std::vector with an l-value? 如何使用size参数初始化std :: vector并使每个对象独立构造? - How can I initialize a std::vector with a size parameter and have each object constructed independently?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM