Я пишу внутренний цикл, который должен разместить struct
в непрерывном хранилище. Я не знаю, сколько таких struct
будет раньше времени. Моя проблема в том, что vector
в STL инициализирует свои значения до 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];
}
}
Еще одно уточнение: это не значит, что конструктор инициализирует значения равными 0. Это изменение размера вызывает insert, что и делает.
Не могли бы вы также дать нам объявление структуры? Спасибо... :-)
ПРИМЕЧАНИЕ: вы все равно не можете получить доступ к неинициализированным данным. Та же проблема для vector <T> после .end () и неинициализированных членов T []. Но с вектором, скорее всего, отладочный код говорит вам об этом сейчас. Код массива на ПК клиента не сработает.
Это хороший вопрос. Для некоторых приложений важно понимать, что std :: vector всегда инициализирует свои элементы, даже если они являются простыми старыми данными (POD).
Также см. stackoverflow.com/q/7218574/1969455 для подхода распределителя. (подходит для типов POD)
Суть проблемы в том, что вектор выполняет "нулевая" инициализация по умолчанию, потому что std::allocator
имеет только значение и нулевую инициализацию, но не имеет метода для инициализации по умолчанию.
Используйте метод std :: vector :: reserve (). Он не изменит размер вектора, но выделит пространство.
Эээ ...
попробуйте метод:
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
Инициализация, которую вы видели, вероятно, была артефактом.
[РЕДАКТИРОВАТЬ] После комментария об изменении размера я изменил код, добавив строку изменения размера. Изменение размера эффективно вызывает конструктор по умолчанию объекта внутри вектора, но если конструктор по умолчанию ничего не делает, тогда ничего не инициализируется ... Я все еще считаю, что это был артефакт (мне впервые удалось обнулить весь вектор с помощью следующий код:
aMyStruct.push_back(MyStruct()) ;
aMyStruct.push_back(MyStruct()) ;
aMyStruct.push_back(MyStruct()) ;
Так... : - /
[РЕДАКТИРОВАТЬ 2] Как уже предлагал Аркадий, решение состоит в использовании встроенного конструктора, принимающего желаемые параметры. Что-то вроде
struct MyStruct
{
MyStruct(int p_d1, int p_d2) : d1(p_d1), d2(p_d2) {}
int d1, d2 ;
} ;
Вероятно, это будет встроено в ваш код.
Но вам в любом случае следует изучить свой код с помощью профилировщика, чтобы убедиться, что этот фрагмент кода является узким местом вашего приложения.
Я написал заметку выше. Это не конструктор вектора, который инициализируется значением 0. Это делает resize ().
В этом случае MyStruct имеет тривиальный конструктор, поэтому ничего не инициализируется. Это может отличаться от ситуации OP.
Я думаю, ты на правильном пути. У меня нет конструктора, определенного в структуре, поэтому его конструктор по умолчанию (я считаю) инициализируется нулем. Я проверю, решит ли проблему добавление конструктора по умолчанию, который ничего не делает.
Если у вас не определен конструктор и все элементы являются типами POD, конструктор ничего не делает. Если элементы не являются POD, он просто вызовет их конструкторы по умолчанию.
Грег Роджерс прав. Я предполагаю, что память была «нулевой» из-за инициализации некоторого процесса, независимого от кода, который я написал. В C++ вы не платите за то, что не используете. Так что, если вы пишете C-подобный код, у вас не должно быть накладных расходов. И векторы неплохо с этим справляются.
Похоже, что в данном случае вектор нас подвел. Мы платим за инициализацию, даже если она нам не нужна или не нужна. Это гарантируется семантикой функции insert (), которая вызывается функцией resize (). Значение, используемое для инициализации, основано на том, что происходит в MyStruct, переданном в resize (). Поскольку вы ничего не указали при вызове resize (), использовался конструктор по умолчанию. Поскольку конструктор по умолчанию в этом случае ничего не делает, вы можете получить нули или что-то еще. В любом случае вы платите за инициализацию, выполняемую resize ().
@nobar: это зависит от конструктора MyStruct. Если он пуст и встроен, а члены MyStruct имеют конструкторы с нулевой стоимостью, компилятор C++ не оптимизирует его до нуля. Тогда мы не будем за это платить. Только для изменения размера.
Должны ли сами структуры находиться в непрерывной памяти, или можно обойтись вектором struct *?
Векторы копируют все, что вы к ним добавляете, поэтому использование векторов указателей, а не объектов, является одним из способов повышения производительности.
Они должны быть смежными. Они находятся в буфере, который будет отправлен по сети одним огромным фрагментом.
Я не думаю, что 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;
}
Итак, вот проблема, изменение размера вызывает метод insert, который выполняет копирование конструируемого элемента по умолчанию для каждого из вновь добавленных элементов. Чтобы получить нулевую стоимость, вам нужно написать свой собственный конструктор по умолчанию И свой собственный конструктор копирования как пустые функции. Выполнение этого с вашим конструктором копирования - это очень плохая идея, потому что это нарушит внутренние алгоритмы перераспределения std :: vector.
Резюме: вы не сможете сделать это с помощью std :: vector.
Это настоящая проблема. std :: vector должен понимать, что ему не нужно выполнять какую-либо инициализацию, если у T есть тривиальный конструктор по умолчанию. Спасибо, что указали, что конструктор копирования - это то, что делает здесь ненужную работу.
Чтобы уточнить ответы на backup (): вам нужно использовать reserve () вместе с push_back (). Таким образом, для каждого элемента вызывается не конструктор по умолчанию, а конструктор копирования. Вы по-прежнему несете штраф, создавая структуру в стеке, а затем копируя ее в вектор. С другой стороны, возможно, что если вы используете
vect.push_back(MyStruct(fieldValue1, fieldValue2))
компилятор создаст новый экземпляр прямо в памяти, принадлежащей вектору. Это зависит от того, насколько умен оптимизатор. Вам нужно проверить сгенерированный код, чтобы узнать.
Оказывается, оптимизатор gcc на уровне O3 недостаточно умен, чтобы избежать копирования.
Судя по вашим комментариям к другим постерам, похоже, что у вас остались malloc () и друзья. Vector не позволит вам иметь неконструктивные элементы.
Из вашего кода похоже, что у вас есть вектор структур, каждая из которых состоит из 2 int. Могли бы вы вместо этого использовать 2 вектора целых чисел? потом
copy(data1, data1 + count, back_inserter(v1));
copy(data2, data2 + count, back_inserter(v2));
Теперь вы не платите за копирование структуры каждый раз.
Интересно. Это может просто сработать - похоже, что это позволит избежать создания промежуточного объекта.
Я бы сделал что-то вроде:
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, но это небольшие затраты, так как это даст вам лучшее из обоих миров; не выполняется ненужная инициализация и перераспределение во время цикла не происходит.
Кажется, это не решает проблему, поскольку он использует временный MyType () и копирует его в вектор. Еще есть двойная инициализация.
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-значения (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));
}
}
Это означает, что вам нужно создать свой собственный векторный класс. В этом случае это также усложняет то, что должно было быть простым примером. Но могут быть случаи, когда использование такой фабричной функции лучше, например, если вставка обусловлена каким-то другим значением, и в противном случае вам пришлось бы безоговорочно построить какое-то дорогостоящее временное устройство, даже если оно на самом деле не было необходимо.
C++ 0x добавляет новый шаблон функции-члена emplace_back
в vector
(который полагается на вариативные шаблоны и идеальную пересылку), который полностью избавляется от любых временных файлов:
memberVector.emplace_back(data1[i], data2[i]);
Если вы действительно настаиваете на том, чтобы элементы не инициализировались и жертвовали некоторыми методами, такими как front (), back (), push_back (), используйте вектор ускорения из числа. Это позволяет даже не сохранять существующие элементы при вызове resize () ...
В C++ 11 (и boost) вы можете использовать версию unique_ptr
с массивом для выделения неинициализированного массива. Это не совсем stl-контейнер, но он по-прежнему управляется памятью и C++ - иш, чего будет достаточно для многих приложений.
auto my_uninit_array = std::unique_ptr<mystruct[]>(new mystruct[count]);
Вы можете использовать тип-оболочку вокруг своего типа элемента с конструктором по умолчанию, который ничего не делает. Например.:
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);
Вы можете использовать новые элементы boost::noinit_adaptor
для инициализация по умолчанию (что не является инициализацией для встроенных типов):
std::vector<T, boost::noinit_adaptor<std::allocator<T>> memberVector;
Пока вы не передаете инициализатор в resize
, он по умолчанию инициализирует новые элементы.
Я не уверен во всех ответах, в которых говорится, что это невозможно, или о неопределенном поведении.
Иногда вам нужно использовать std :: vector. Но когда-нибудь вы знаете его окончательный размер. И вы также знаете, что ваши элементы будут построены позже. Пример: когда вы сериализуете содержимое вектора в двоичный файл, а затем читаете его позже. У Unreal Engine есть TArray :: setNumUninitialized, почему бы не std :: vector?
Чтобы ответить на первоначальный вопрос «Есть ли способ предотвратить инициализацию или существует контейнер, подобный STL, с непрерывным хранилищем изменяемого размера и неинициализированными элементами?»
и да и нет. Нет, потому что STL не предлагает способа сделать это.
Да, потому что мы кодируем на C++, а C++ позволяет многое. Если вы готовы быть плохим парнем (и если вы действительно знаете, что делаете). Вы можете угнать вектор.
Вот пример кода, который работает только для реализации STL Windows, для другой платформы, посмотрите, как реализован std :: vector для использования его внутренних членов:
// This macro is to be defined before including VectorHijacker.h. Then you will be able to reuse the VectorHijacker.h with different objects.
#define HIJACKED_TYPE SomeStruct
// VectorHijacker.h
struct VectorHijacker
{
std::size_t _newSize;
};
template<>
template<>
inline decltype(auto) std::vector<HIJACKED_TYPE, std::allocator<HIJACKED_TYPE>>::emplace_back<const VectorHijacker &>(const VectorHijacker &hijacker)
{
// We're modifying directly the size of the vector without passing by the extra initialization. This is the part that relies on how the STL was implemented.
_Mypair._Myval2._Mylast = _Mypair._Myval2._Myfirst + hijacker._newSize;
}
inline void setNumUninitialized_hijack(std::vector<HIJACKED_TYPE> &hijackedVector, const VectorHijacker &hijacker)
{
hijackedVector.reserve(hijacker._newSize);
hijackedVector.emplace_back<const VectorHijacker &>(hijacker);
}
Но будьте осторожны, мы говорим об угоне. Это действительно грязный код, и его следует использовать только в том случае, если вы действительно знаете, что делаете. Кроме того, он не переносится и сильно зависит от того, как была реализована реализация STL.
Я не буду советовать вам его использовать, потому что все здесь (включая меня) хорошие люди. Но я хотел сообщить вам, что это возможно, вопреки всем предыдущим ответам, в которых говорилось, что это не так.
Обратите внимание, что я написал код в Visual Studio 2019 Community 16.7.5. И я использовал последний черновик ... И режим соответствия отключен (разрешающий-) (потому что, к сожалению, шаблон и режим соответствия во многих случаях не очень хорошая комбинация). Наконец, он зависит от того, как платформа реализовала STL, поэтому вам нужно будет адаптировать решение, если вы хотите его использовать (но вы не пачкаете руки, не так ли?).
Примечание. Использование функции reserve () не является решением, поскольку вы не можете легально получить доступ к данным, которые находятся в местоположениях end () и выше.