STL-векторы с неинициализированным хранилищем?

Я пишу внутренний цикл, который должен размещать struct в непрерывном хранилище. Я не знаю, сколько из этих struct будет раньше времени. Моя проблема заключается в том, что STL vector инициализирует свои значения равными 0, поэтому независимо от того, что я делаю, я беру на себя стоимость инициализации плюс стоимость установки членов struct на их значения.

Есть ли способ предотвратить инициализацию, или есть ли подобный STL-контейнер контейнер с изменяемыми размерами и неинициализированные элементы?

(Я уверен, что эту часть кода нужно оптимизировать, и я уверен, что инициализация является значительной стоимостью.)

Также см. мои комментарии ниже для пояснения о том, когда происходит инициализация.

НЕКОТОРЫЙ КОД:

void GetsCalledALot(int* data1, int* data2, int count) {
    int mvSize = memberVector.size()
    memberVector.resize(mvSize + count); // causes 0-initialization

    for (int i = 0; i < count; ++i) {
        memberVector[mvSize + i].d1 = data1[i];
        memberVector[mvSize + i].d2 = data2[i];
    }
}

Ответ 1

std::vector должен как-то инициализировать значения в массиве, что означает, что нужно вызвать конструктор (или конструктор-копию). Поведение vector (или любого класса контейнера) undefined, если вы должны получить доступ к неинициализированному разделу массива, как если бы он был инициализирован.

Лучший способ - использовать reserve() и push_back(), чтобы использовать конструктор-копию, избегая построения по умолчанию.

Используя код примера:

struct YourData {
    int d1;
    int d2;
    YourData(int v1, int v2) : d1(v1), d2(v2) {}
};

std::vector<YourData> memberVector;

void GetsCalledALot(int* data1, int* data2, int count) {
    int mvSize = memberVector.size();

    // Does not initialize the extra elements
    memberVector.reserve(mvSize + count);

    // Note: consider using std::generate_n or std::copy instead of this loop.
    for (int i = 0; i < count; ++i) {
        // Copy construct using a temporary.
        memberVector.push_back(YourData(data1[i], data2[i]));
    }
}

Единственная проблема с вызовом reserve() (или resize()) - это то, что вы можете в конечном итоге вызвать конструктор-копию чаще, чем вам нужно. Если вы можете сделать хорошее предсказание относительно конечного размера массива, лучше reserve() пробел один раз в начале. Если вы не знаете окончательный размер, хотя, как минимум, количество копий будет минимальным в среднем.

В текущей версии С++ внутренний цикл немного неэффективен, поскольку временное значение создается в стеке, копируется в память векторов и, наконец, временное уничтожается. Однако следующая версия С++ имеет функцию R-Value reference (T&&), которая поможет.

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

template <typename T>
class my_vector_replacement {

    // ...

    template <typename F>
    my_vector::push_back_using_factory(F factory) {
        // ... check size of array, and resize if needed.

        // Copy construct using placement new,
        new(arrayData+end) T(factory())
        end += sizeof(T);
    }

    char* arrayData;
    size_t end; // Of initialized data in arrayData
};

// One of many possible implementations
struct MyFactory {
    MyFactory(int* p1, int* p2) : d1(p1), d2(p2) {}
    YourData operator()() const {
        return YourData(*d1,*d2);
    }
    int* d1;
    int* d2;
};

void GetsCalledALot(int* data1, int* data2, int count) {
    // ... Still will need the same call to a reserve() type function.

    // Note: consider using std::generate_n or std::copy instead of this loop.
    for (int i = 0; i < count; ++i) {
        // Copy construct using a factory
        memberVector.push_back_using_factory(MyFactory(data1+i, data2+i));
    }
}

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

Ответ 2

С++ 0x добавляет новый шаблон функции-члена emplace_back to vector (который опирается на вариативные шаблоны и совершенную переадресацию), который полностью избавляется от любых временных рядов:

memberVector.emplace_back(data1[i], data2[i]);

Ответ 3

Чтобы уточнить ответы reserve(): вам нужно использовать reserve() в сочетании с push_back(). Таким образом, конструктор по умолчанию не вызывается для каждого элемента, а скорее конструктор копирования. Вы по-прежнему несете штраф за настройку своей структуры на стек, а затем копируете ее в вектор. С другой стороны, возможно, что если вы используете

vect.push_back(MyStruct(fieldValue1, fieldValue2))

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

Ответ 4

В С++ 11 (и boost) вы можете использовать версию массива unique_ptr для выделения неинициализированного массива. Это не совсем контейнер stl, но по-прежнему управляется памятью и С++ - ish, что будет достаточно хорошим для многих приложений.

auto my_uninit_array = std::unique_ptr<mystruct[]>(new mystruct[count]);

Ответ 5

Таким образом, проблема, resize вызывает вызов insert, который выполняет построение копии из построенного по умолчанию элемента для каждого из вновь добавленных элементов. Чтобы получить это значение 0, вам нужно написать собственный конструктор по умолчанию И свой собственный конструктор копирования как пустые функции. Выполнение этого для вашего конструктора копий - это очень плохая идея, потому что он нарушит внутренние алгоритмы перераспределения std::vector.

Сводка: вы не сможете сделать это с помощью std::vector.

Ответ 6

Err...

попробуйте метод:

std::vector<T>::reserve(x)

Это позволит вам зарезервировать достаточно памяти для элементов x без инициализации (ваш вектор по-прежнему пуст). Таким образом, перераспределения не будет переходить на x.

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

После проверки на g++ следующий код:

#include <iostream>
#include <vector>

struct MyStruct
{
   int m_iValue00 ;
   int m_iValue01 ;
} ;

int main()
{
   MyStruct aaa, bbb, ccc ;

   std::vector<MyStruct> aMyStruct ;

   aMyStruct.push_back(aaa) ;
   aMyStruct.push_back(bbb) ;
   aMyStruct.push_back(ccc) ;

   aMyStruct.resize(6) ; // [EDIT] double the size

   for(std::vector<MyStruct>::size_type i = 0, iMax = aMyStruct.size(); i < iMax; ++i)
   {
      std::cout << "[" << i << "] : " << aMyStruct[i].m_iValue00 << ", " << aMyStruct[0].m_iValue01 << "\n" ;
   }

   return 0 ;
}

дает следующие результаты:

[0] : 134515780, -16121856
[1] : 134554052, -16121856
[2] : 134544501, -16121856
[3] : 0, -16121856
[4] : 0, -16121856
[5] : 0, -16121856

Инициализация, которую вы видели, была, вероятно, артефактом.

[EDIT] После комментария об изменении размера я изменил код, чтобы добавить строку изменения размера. Изменение размера эффективно вызывает конструктор по умолчанию для объекта внутри вектора, но если конструктор по умолчанию ничего не делает, то ничего не инициализируется... Я все же считаю, что это был артефакт (мне удалось в первый раз получить весь вектор с нулевым значением следующий код:

aMyStruct.push_back(MyStruct()) ;
aMyStruct.push_back(MyStruct()) ;
aMyStruct.push_back(MyStruct()) ;

Итак... : -/

[EDIT 2] Как уже было предложено Аркадием, решение состоит в том, чтобы использовать встроенный конструктор с требуемыми параметрами. Что-то вроде

struct MyStruct
{
   MyStruct(int p_d1, int p_d2) : d1(p_d1), d2(p_d2) {}
   int d1, d2 ;
} ;

Это, вероятно, будет включено в ваш код.

Но вы все равно должны изучить свой код с профилировщиком, чтобы убедиться, что этот фрагмент кода является узким местом вашего приложения.

Ответ 7

Используйте метод std::vector:: reserve(). Он не будет изменять размер вектора, но он выделит пространство.

Ответ 8

Из ваших комментариев к другим плакатам, похоже, что вы остаетесь с malloc() и друзьями. Вектор не позволит вам иметь незастроенные элементы.

Ответ 9

Из вашего кода, похоже, у вас есть вектор structs, каждый из которых содержит 2 ints. Не могли бы вы использовать 2 вектора ints? Тогда

copy(data1, data1 + count, back_inserter(v1));
copy(data2, data2 + count, back_inserter(v2));

Теперь вы не платите за копирование структуры каждый раз.

Ответ 10

Если вы действительно настаиваете на том, чтобы элементы не инициализировались и жертвовали некоторыми методами, такими как front(), back(), push_back(), используйте вектор boost из числа. Это позволяет даже не сохранять существующие элементы при вызове resize()...

Ответ 11

Вы можете использовать тип оболочки вокруг вашего типа элемента с конструктором по умолчанию, который ничего не делает. Например:.

template <typename T>
struct no_init
{
    T value;

    no_init() { static_assert(std::is_standard_layout<no_init<T>>::value && sizeof(T) == sizeof(no_init<T>), "T does not have standard layout"); }

    no_init(T& v) { value = v; }
    T& operator=(T& v) { value = v; return value; }

    no_init(no_init<T>& n) { value = n.value; }
    no_init(no_init<T>&& n) { value = std::move(n.value); }
    T& operator=(no_init<T>& n) { value = n.value; return this; }
    T& operator=(no_init<T>&& n) { value = std::move(n.value); return this; }

    T* operator&() { return &value; } // So you can use &(vec[0]) etc.
};

Для использования:

std::vector<no_init<char>> vec;
vec.resize(2ul * 1024ul * 1024ul * 1024ul);

Ответ 12

Нужно ли самим структурам находиться в непрерывной памяти, или вы можете уйти с вектором struct *?

Векторы создают копию того, что вы добавляете к ним, поэтому использование векторов указателей, а не объектов, является одним из способов повышения производительности.

Ответ 13

Я не думаю, что STL - ваш ответ. Вам нужно будет свернуть свое собственное решение, используя realloc(). Вам нужно будет сохранить указатель, либо размер, либо количество элементов, и использовать это, чтобы найти, где начать добавлять элементы после realloc().

int *memberArray;
int arrayCount;
void GetsCalledALot(int* data1, int* data2, int count) {
    memberArray = realloc(memberArray, sizeof(int) * (arrayCount + count);
    for (int i = 0; i < count; ++i) {
        memberArray[arrayCount + i].d1 = data1[i];
        memberArray[arrayCount + i].d2 = data2[i];
    }
    arrayCount += count;
}

Ответ 14

Я бы сделал что-то вроде:

void GetsCalledALot(int* data1, int* data2, int count)
{
  const size_t mvSize = memberVector.size();
  memberVector.reserve(mvSize + count);

  for (int i = 0; i < count; ++i) {
    memberVector.push_back(MyType(data1[i], data2[i]));
  }
}

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