Как я могу избежать std::vector <> для инициализации всех его элементов?

EDIT: Я отредактировал и вопрос, и его название, чтобы быть более точным.

Учитывая следующий исходный код:

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

... как я могу избежать выделения памяти, выделенной вектором < > для инициализации копиями своего первого элемента, который является операцией O (n), которую я бы скорее пропустил ради скорости, поскольку мой дефолт конструктор ничего не делает?

Решение, специфичное для g++, будет выполнено, если не существует универсального (но я не смог найти какой-либо атрибут для этого).

EDIT: сгенерированный код следует (командная строка: arm-elf-g++ - 4.5 -O3 -S -fno-verbose-asm -o - test.cpp | arm-elf-С++ filter | 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: Для полноты, вот сборка для 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: Я думаю, что вывод заключается в том, чтобы не использовать std::vector<>, когда вы хотите избежать ненужной инициализации. Я закончил разворачивать свой собственный шаблонный контейнер, который работает лучше (и имеет специализированные версии для neon и armv7).

Ответ 1

Инициализация выделенных элементов контролируется аргументом шаблона Allocator, если вам нужно его настроить, настройте его. Но помните, что это может легко уйти в область грязного взлома, поэтому используйте с осторожностью. Например, вот довольно грязное решение. Это позволит избежать инициализации, но, скорее всего, будет хуже в производительности, но ради демонстрации (поскольку люди сказали, что это невозможно!... невозможно не в словаре программиста на С++!):

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 от вызова конструктора-копии для инициализации ваших элементов, находится в функции построения объекта вектор-распределителя.

Кроме того, вы можете покончить с "коммутатором" и просто сделать "no-init-allocator", но затем вы также отключите построение копии, которое необходимо для копирования данных во время изменения размера (что сделало бы это векторный класс гораздо менее полезен).

Ответ 2

Это странный угол vector. Проблема заключается не в том, что ваш элемент инициализируется значением... это то, что случайный контент в первом прототипном элементе копируется ко всем остальным элементам вектора. (Это поведение изменилось с С++ 11, значение которого инициализирует каждый элемент).

Это (/было) сделано по уважительной причине: рассмотрим некоторый ссылочный подсчитанный объект... если вы построите vector запрос на 1000 элементов, инициализированных таким объектом, вы, очевидно, хотите, чтобы один объект со ссылочным числом 1000, вместо того, чтобы иметь 1000 независимых "клонов". Я говорю "очевидно", потому что, сделав ссылку на объект, подсчитанную в первую очередь, подразумевает, что это очень желательно.

В любом случае, вам почти не повезло. Фактически, vector обеспечивает, чтобы все элементы были одинаковыми, даже если контент, который он синхронизирует, является неинициализированным мусором.


В стране с нестандартным g++-специфическим счастливым взломом мы можем использовать любую публичную шаблонную функцию-член в интерфейсе vector в качестве бэкдора для изменения данных частного члена, просто специализируясь на шаблоне для какого-либо нового типа.

ПРЕДУПРЕЖДЕНИЕ: не только для этого "решения", но и для всего этого, чтобы избежать построения по умолчанию... не делайте этого для типов с важными инвариантами - вы разрушаете инкапсуляцию и можете легко иметь vector или какую-либо операцию, которую вы пытаетесь вызвать operator=(), copy-constructors и/или деструкторы, где аргументы *this/left и/или right-side не соблюдают эти инварианты. Например, избегайте типов значений с указателями, которые вы ожидаете быть 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 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, а точные имена/импорт частных членов могут меняться в любое время....

Ответ 3

Вы переносите все свои примитивы в структуру:

struct IntStruct
{
    IntStruct();

    int myInt;
}

с IntStruct(), определяемым как пустой конструктор. Таким образом, вы объявляете v как IntStruct v;, поэтому, когда a vector of xyzs все значения инициализируются, все, что они делают, это инициализация значения v, которая является не-op.

EDIT: Я неправильно понял вопрос. Это то, что вы должны делать, если у вас есть vector примитивных типов, потому что vector определяется значением-initialize при создании элементов с помощью метода resize(). Структуры не требуются для инициализации своих членов при построении, хотя эти "неинициализированные" значения по-прежнему могут быть установлены на 0 чем-то другим - эй, они могут быть любыми.

Ответ 4

Я не вижу инициализацию памяти. Конструктор 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

EDIT:

Если вы просто пытаетесь инициализировать вектор некоторой нетривиальной вещью и не хотите тратить время на создание по умолчанию своего содержимого, вы можете попробовать создать пользовательский итератор и передать его векторному конструктору.

Модифицированный пример:

#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

Ответ 5

Для справки следующий код приводит к оптимальной сборке в g++: Я не говорю, что когда-либо буду использовать его, и я не призываю вас. Это не правильно С++! Это очень и очень грязный хак! Я думаю, что это может даже зависеть от версии 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;
}

EDIT: реализованный _Vector_impl был общедоступным, что упростило ситуацию.

EDIT:. Это созданная сборка ARM для xyz_create(), скомпилированная с -fno-исключениями (demangled using С++ 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

Ответ 6

Вы не можете избежать инициализации элементов std::vector.

По этой причине я использую производный класс std::vector.  resize() реализован в этом примере. Вы должны также реализовать конструкторы.

Хотя это не стандартная С++, а реализация компилятора: - (

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

Ответ 7

Мне также любопытно. Вы просто хотите, чтобы память была инициализирована случайным образом?

Векторные элементы хранятся в последовательных ячейках памяти, поэтому возможна случайная инициализация.

Ответ 8

Если вам нужен вектор с зарезервированной памятью, но без инициализированных элементов, используйте reserve вместо конструктора:

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

Ответ 9

Когда ваш 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;
};