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

РЕДАКТИРОВАТЬ: Я отредактировал и вопрос, и его название, чтобы быть более точным.

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

#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 ++, если общего не существует (но я не смог найти ни одного атрибута для этого).

РЕДАКТИРОВАТЬ: сгенерированный код следует (командная строка: arm-elf-g ++ - 4.5 -O3 -S -fno-verbose-asm -o - test.cpp | arm-elf-c ++ filter | grep -vE '^ [[: пробел:]] + [. @]. * $')

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).


person jcayzac    schedule 11.05.2011    source источник
comment
Чисто из любопытства: почему?   -  person ildjarn    schedule 11.05.2011
comment
g ++ не инициализирует значения автоматически до 0. Некоторые компиляторы делают это в режиме отладки, но не в оптимизированном режиме.   -  person Romain Hippeau    schedule 11.05.2011
comment
@ildjarn: потому что заполнение памяти нулями требует затрат, и я бы хотел, чтобы компилятор уважал мой выбор, когда я оставляю значение без инициализации.   -  person jcayzac    schedule 11.05.2011
comment
@jcayzac: это может быть базовый распределитель памяти ОС, выполняющий нулевую инициализацию. Если так, то это неизбежная (и, предположительно, чрезвычайно дешевая) цена.   -  person ildjarn    schedule 11.05.2011
comment
Ваше предположение, что v является инициализированным значением, неверно: это не инициализированное значение. Компилятор, библиотека, среда выполнения или операционная система могут обнулить память за вас, но это не инициализация значения.   -  person James McNellis    schedule 11.05.2011
comment
@Romain: Предлагаю вам взглянуть на сборку, созданную g ++ -O3 для приведенного выше кода. Вы увидите цикл заполнения памяти. По крайней мере, на арках x86_64 и armv7-a.   -  person jcayzac    schedule 11.05.2011
comment
Не вижу инициализацию на i686-apple-darwin10-g ++ - 4.2.1.   -  person Mike DeSimone    schedule 11.05.2011
comment
@Mike: странно. Я вижу это на i686-apple-darwin10-g ++ - 4.2.1 на моем компьютере.   -  person jcayzac    schedule 11.05.2011
comment
vector - это непрерывная последовательность значений. Если вы не хотите инициализировать vector значениями, разве пустой вектор не соответствует вашим требованиям? Я просто пытаюсь понять ваш вариант использования.   -  person CB Bailey    schedule 11.05.2011
comment
@jcayzac: из любопытства, почему бы тогда не просто вызвать reserve, а не помещать элементы? Или, может быть, как использовать vector, не инициализированный впоследствии?   -  person Matthieu M.    schedule 11.05.2011


Ответы (9)


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

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

person Mikael Persson    schedule 11.05.2011
comment
Я только что протестировал эту идею, изменив ваш код для принудительного применения should_init == false (поэтому распределитель называется uninitialized_allocator), но это не сработало. По-прежнему производит ту же сборку. - person jcayzac; 11.05.2011
comment
Я добавил asm volatile (испускаемая конструкция @); in construct (), и эта строка никогда не отображается в сборке, что означает, что никакой код никогда не вызывает этот метод. - person jcayzac; 11.05.2011
comment
Мне удалось заставить его работать (с версией без инициализации), определив распределитель с нуля (в отличие от производного от std :: allocator). Проблема заключается в шаблоне класса повторного связывания, который вложен в std :: allocator. И при этом действительно не происходит инициализации, только malloc (), а затем free () (часть моих простых функций выделения памяти без инициализации). - person Mikael Persson; 11.05.2011
comment
Я добавил asm-комментарии в другие методы, а конструктор и деструктор распределителя по умолчанию - это единственные два вызова, выполняемые вектором. Что касается шаблонного конструктора копирования, я думаю, что GNU libstdc ++ использует класс-член шаблона с именем rebind. Что касается construct(), я все еще смотрю его файлы заголовков, где все кажется сильно запутанным ... Похоже, ваше переопределение ничего не отменяет в GNU-версии std::allocator<>. - person jcayzac; 11.05.2011
comment
см. мое добавление шаблона класса rebind (это формальное требование, я просто забыл о нем, и он заставил векторную сортировку вернуться к std :: allocator внутри его внутренностей). - person Mikael Persson; 11.05.2011
comment
Одна из моих идей заключалась в том, чтобы сначала создать вектор с помощью этого класса uninitialized_allocator, создать второй пустой вектор с помощью std :: allocator, а затем поменять их местами перед возвратом второго. Но vector :: swap () требует, чтобы исходный вектор использовал тот же распределитель, что и целевой, для согласованности распределения / свободы, я думаю, поэтому это невозможно :-( - person jcayzac; 11.05.2011
comment
Предлагаемое вами решение в основном работает и пока является единственным, поэтому я считаю его лучшим ответом. Но следует отметить, что компилятор не может правильно встроить векторные методы из-за зависимости от этого логического значения и будет генерировать множество вызовов функций и т. Д., Что противоречит исходной цели оптимизации. - person jcayzac; 11.05.2011
comment
@jcayzac: FYI / мое недавнее изменение также добавляет рабочее и производительное решение, хотя и непереносимое ;-P - person Tony Delroy; 11.05.2011
comment
@Tony: Ах, я только что увидел это после того, как опубликовал свой ^^ - person jcayzac; 11.05.2011
comment
Ваш код дает ошибку сегментации для этой простой программы int main(){ my_vector<double> v(1024); //this won't initialize the memory at all. std::cout << "v[4] = " << v[4] << std::endl; }. Я пробовал как с C ++ 14, так и с C ++ 03 (мне пришлось внести незначительные изменения, чтобы он скомпилировался). - person alfC; 19.04.2017

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

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

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


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

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

person Tony Delroy    schedule 11.05.2011
comment
Согласованный; вызываемый конструктор std::vector<> по умолчанию инициализирует экземпляр xyz и инициализирует копированием каждый из 1024 элементов в std::vector<>, используя это значение в качестве источника. Это чистое совпадение, что для OP инициализация по умолчанию xyz в этом конкретном случае приводит к тому, что xyz::v становится 0. - person ildjarn; 11.05.2011
comment
Это объясняет странное значение 1606418432, увиденное Майком. С точки зрения оптимизации, это сильно отталкивает. Инициализация памяти, которая в любом случае будет перезаписана позже в коде, звучит как большая трата ресурсов. - person jcayzac; 11.05.2011
comment
Я могу подтвердить, что после очистки конструктора копирования и оператора присваивания память не затрагивается. Но тогда я больше не могу назначать экземпляры и должен назначать переменные-члены напрямую (или, возможно, с помощью сеттеров и геттеров). Фу. :-( - person jcayzac; 11.05.2011
comment
Если вы собираетесь передать вектор, не инициализированный, чему-то еще, возможно, вам не нужен вектор, а действительно нужен массив, то есть new int[1024]? - person Mike DeSimone; 11.05.2011
comment
вы, очевидно, хотите, чтобы был скопирован один прототип объекта. Это совсем не очевидно, и на самом деле это уже не поведение в C ++ 0x. В C ++ 0x вместо копирования, конструирующего N элементов, этот конструктор value теперь инициализирует N элементов. - person James McNellis; 11.05.2011
comment
@Mike: из-за отсутствия такой вещи в текущей версии C ++ я использую std :: vector ‹› как массив с динамическим размером, который заботится о реаллоках и т. Д. Я считаю, что это было даже первоначальное намерение, поэтому я не понимаю, почему у них нет конструктора без значения по умолчанию, который просто вызывал бы конструктор по умолчанию для каждого элемента (чтобы компилятор мог оптимизировать этот цикл) . - person jcayzac; 11.05.2011
comment
Вероятно, потому, что слишком много неаккуратных программистов не понимали, что таким образом они создают неинициализированные объекты. Точно так же, как в старые времена раздел .bss не инициализировался средой выполнения, в отличие от сегодняшнего дня, когда он инициализируется значением 0. - person Mike DeSimone; 11.05.2011
comment
@jcayzac: Что ж, хорошие новости: если у вас есть реализация стандартной библиотеки C ++, совместимая с C ++ 0x, это именно то, что делает этот конструктор. :-) - person James McNellis; 11.05.2011
comment
@ Джеймс: весь смысл подсчета ссылок состоит в том, чтобы иметь меньше копий идентичных объектов, поэтому для таких элементов мне все еще кажется, что в целом ожидалось бы не копировать. Тем не менее, это приводит к тому, что COW плохо работает для строк в многопоточных средах ... большая тема. - person Tony Delroy; 11.05.2011
comment
@jcayzac: Я полностью согласен, это неприятно ... вам не нужно платить за инициализацию, которую вы не хотите. Кстати / умно поэкспериментировать с удалением оператора копирования-конструктора / присваивания ... хороший способ исследовать проблему, к сожалению, это не часто полезно в качестве решения. - person Tony Delroy; 11.05.2011
comment
@James: хм .. добавление -std = c ++ 0x привело к точно такой же сборке. Я думаю, что gcc libstdc ++ еще не поддерживает его в достаточной степени. - person jcayzac; 11.05.2011
comment
@Tony: обратите внимание, я не удалял их. Их удаление ничего не изменило. Вместо этого я предоставил их пустую реализацию (конструктор копирования ничего не делает, оператор присваивания просто возвращает * this). Но да, это и так совершенно бесполезно. - person jcayzac; 11.05.2011
comment
Правильным решением для этого было бы использование std :: vector объекта функции шаблона для выполнения копирования, чтобы он мог быть специализированным, чтобы ничего не делать ... Где я могу отправлять предложения для c ++ 2z? ^ _ ^ - person jcayzac; 11.05.2011
comment
@jcayzac: у всех контейнеров STL уже есть аргумент шаблона для этого. Это то, для чего нужен аргумент Allocator, чтобы настроить выделение и создание копирования (и освобождение / уничтожение). Смотрите мой ответ. - person Mikael Persson; 11.05.2011
comment
@jcayzac @Mikael: дело в том, что вы не обязательно хотите использовать правильные методы построения / копирования для всех операций для vector - это просто полезно для начального определения размера (и, конечно, только когда тип элемента - POD). (Другая проблема может заключаться в том, что функции, которые вы хотите использовать, ожидают vector<X>, которого у вас больше не будет) - person Tony Delroy; 11.05.2011
comment
@Tony: Это, безусловно, правда. Но главный вопрос - это способ создать std::vector неинициализированных элементов, я не знаю другого способа сделать это, кроме как создать собственный распределитель (независимо от того, насколько глупо это сделать для этой цели). - person Mikael Persson; 11.05.2011

Вы оборачиваете все свои примитивы в структуру:

struct IntStruct
{
    IntStruct();

    int myInt;
}

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

РЕДАКТИРОВАТЬ: Я неправильно понял вопрос. Это то, что вам следует делать, если у вас есть vector примитивных типов, потому что vector определен для инициализации значения при создании элементов с помощью метода resize(). Структуры не обязаны инициализировать значения своих членов при построении, хотя эти «неинициализированные» значения все равно могут быть установлены в 0 с помощью чего-то еще - эй, они могут быть чем угодно.

person rotanimod    schedule 11.05.2011

Я не вижу, чтобы память была инициализирована. Конструктор 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
person Mike DeSimone    schedule 11.05.2011
comment
Вы только что показали, что он инициализирован. Только до значения 1606418432 (которое объясняет Тони). - person jcayzac; 11.05.2011

Для справки, следующий код приводит к оптимальной сборке в 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 был общедоступным, что упрощает ситуацию.

РЕДАКТИРОВАТЬ: вот сгенерированная сборка ARM для xyz_create (), скомпилированная с -fno-exceptions (разобранная с помощью c ++ filter) и без какого-либо цикла инициализации памяти:

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
person jcayzac    schedule 11.05.2011
comment
ну, ты меня сильно испачкал ... болван! Какой болезненный способ исправить это ...! ; -} - person Tony Delroy; 11.05.2011

Вы не можете избежать инициализации элементов 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;
        }
    }
};
person Chameleon    schedule 02.10.2012

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

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

person Aater Suleman    schedule 11.05.2011
comment
Я не хочу, чтобы память была инициализирована случайным образом, я хочу, чтобы она вообще не инициализировалась. Я не хочу, чтобы моя сгенерированная сборка содержала цикл, заполняющий память чем-либо. Я хочу, чтобы его не трогали. - person jcayzac; 11.05.2011

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

std::vector<xyz> v;
v.reserve(1024);
assert(v.capacity() >= 1024);
assert(v.size() == 0);
person Éric Malenfant    schedule 11.05.2011
comment
... но тогда вы не можете его использовать ^^ - person jcayzac; 11.05.2011
comment
Нет гарантии, что зарезервированная область не будет инициализирована 0 средой выполнения или ОС. - person Mike DeSimone; 11.05.2011

В связи с тем, как ваш 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;
};
person Jason    schedule 11.05.2011
comment
Nitpick: не существует конструктора int. Только типы классов имеют конструкторы, а int не является типом класса. Ни в коем случае OP явно не инициализирует объект int, что означает, что он остается неинициализированным. - person James McNellis; 11.05.2011
comment
А как насчет C ++ 03 Standard 8.5 / 5? Разве здесь не говорится, что v должен быть установлен в 0 и во втором случае? - person jcayzac; 11.05.2011
comment
Вот мое понимание раздела 8.5 ... если вы пишете xyz object; или xyz object = xyz();, то вызывается конструктор по умолчанию для xyz. В случае № 1, где есть явный список инициализации членов, который инициализирует значение v равным 0, но в случае № 2, поскольку конструктор по умолчанию не инициализирует v, его значение будет неопределенным. - person Jason; 12.05.2011