我正在编写一个内部循环,需要将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];
}
}
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引用(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));
}
}
这样做确实意味着您必须创建自己的向量类。在这种情况下,它也应该使一个简单的示例变得复杂。但是有时候,使用这样的工厂函数可能会更好,例如,如果insert以其他值作为条件,并且您将不得不无条件地构造一些昂贵的临时对象,即使实际上并不需要它。
C ++ 0x向emplace_back
添加了新的成员函数模板vector
(它依赖可变参数模板和完美的转发),从而完全摆脱了任何临时成员:
memberVector.emplace_back(data1[i], data2[i]);
在C ++ 11(和增强版)中,您可以使用数组版本的unique_ptr
分配未初始化的数组。这不是一个stl容器,但是仍然是内存管理和C ++风格的,对于许多应用程序来说已经足够了。
auto my_uninit_array = std::unique_ptr<mystruct[]>(new mystruct[count]);
要澄清reserve()的响应:您需要将reserve()与push_back()结合使用。这样,不是为每个元素调用默认构造函数,而是为副本构造函数调用。您仍然要承担在堆栈上设置结构,然后将其复制到向量的代价。另一方面,如果您使用
vect.push_back(MyStruct(fieldValue1, fieldValue2))
编译器将直接在向量的内存中直接构造新实例。这取决于优化器的智能程度。您需要检查生成的代码以找出答案。
所以这里的问题是,resize调用了insert,它正在为每个新添加的元素从默认构造的元素进行复制构造。为了使这个成本为0,您需要编写自己的默认构造函数和自己的副本构造函数作为空函数。对您的副本构造函数执行此操作是一个[[非常糟糕的主意,因为它将破坏std :: vector的内部重新分配算法。
摘要:您将无法使用std :: vector进行此操作。尝试方法:
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]]在对调整大小发表评论之后,我修改了代码以添加调整大小行。调整大小有效地调用了vector内部对象的默认构造函数,但是如果默认构造函数不执行任何操作,则不会初始化任何内容...我仍然相信这是一个伪像(我设法第一次将整个向量与以下代码:
aMyStruct.push_back(MyStruct()) ; aMyStruct.push_back(MyStruct()) ; aMyStruct.push_back(MyStruct()) ;
所以...:-/[编辑2]就像Arkadiy已经提供的那样,解决方案是使用带有所需参数的内联构造函数。有点像
struct MyStruct { MyStruct(int p_d1, int p_d2) : d1(p_d1), d2(p_d2) {} int d1, d2 ; } ;
这可能会内联到您的代码中。但是无论如何,您都应该使用探查器研究代码,以确保这段代码是应用程序的瓶颈。
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);
copy(data1, data1 + count, back_inserter(v1));
copy(data2, data2 + count, back_inserter(v2));
现在您不必每次都为复制结构付费。
boost::noinit_adaptor
到default initialize新元素(内置类型无需初始化):