2008-09-18 1 views
34

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

Есть ли способ предотвратить инициализацию, или есть ли подобный 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

Примечание - использование резерва() не является решением проблемы, так как вы не можете легально получить доступ к данным, что это в местах конца() и выше. – 2008-09-18 20:36:23

+1

Другое уточнение: это не значит, что конструктор инициализирует значения равными 0. Это то, что делает resize вызывает insert, что делает. – 2008-09-18 20:42:06

+0

Не могли бы вы также дать нам объявление структуры? Спасибо ... :-) – paercebal 2008-09-18 21:00:26

ответ

23

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

Лучший способ - использовать 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() пробел один раз в начале. Если вы не знаете окончательный размер, хотя, по крайней мере, количество копий будет минимальным в среднем.

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

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

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

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

1

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

3

Err ...

попробовать метод:

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

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

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

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

#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] как уже предлагают Arkadiy, решение использовать встроенный конструктор принимает необходимые параметры Нечто подобное

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

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

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

+0

Я написал примечание выше. Это не конструктор вектора, который инициализирует 0. Это изменение размера(), которое делает. – 2008-09-18 20:46:52

0

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

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

0

Я не думаю, что 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; 
} 
4

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

Реферат: Вы не сможете это сделать с помощью std :: vector.

8

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

vect.push_back(MyStruct(fieldValue1, fieldValue2)) 

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

1

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

1

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

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

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

0

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

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

10

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

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

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

6

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

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

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

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