关于具有未初始化存储的c ++:STL向量?

关于具有未初始化存储的c ++:STL向量?

STL vectors with uninitialized storage?

我正在编写一个内部循环,该循环需要将struct放置在连续存储中。 我不知道这些struct会提前多少。 我的问题是STL的vector将其值初始化为0,所以无论我做什么,我都会承担初始化费用以及将struct成员设置为其值的费用。

是否有任何方法可以防止初始化,或者那里有一个带有STL的容器,该容器具有可调整大小的连续存储和未初始化的元素?

(我确定需要对代码的这一部分进行优化,并且我确定初始化会花费大量的成本。)

另外,请参阅以下我的评论以了解有关何时进行初始化的说明。

某些代码:

1
2
3
4
5
6
7
8
9
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(),以便使用复制构造函数,从而避免使用默认构造。

使用示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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 ++中实现该模式的大致示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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 ++ 11(和增强版)中,可以使用unique_ptr的数组版本分配未初始化的数组。这不是一个stl容器,但是仍然是内存管理和C ++风格的,对于许多应用程序来说已经足够了。

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


C ++ 0x在vector上添加了一个新的成员函数模板emplace_back(依赖可变参数模板和完美的转发),该模板完全摆脱了任何临时对象:

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

要阐明reserve()的响应:您需要将reserve()与push_back()结合使用。这样,不是为每个元素调用默认构造函数,而是为副本构造函数调用。您仍然要承担在堆栈上设置结构,然后将其复制到向量的代价。另一方面,如果您使用

1
vect.push_back(MyStruct(fieldValue1, fieldValue2))

编译器将直接在向量的内存中构造新实例。这取决于优化器的智能程度。您需要检查生成的代码以找出答案。


因此,这里的问题是,resize调用了insert,它正在为每个新添加的元素从默认构造的元素进行复制构造。为了使此成本为0,您需要将自己的默认构造函数和自己的副本构造函数编写为空函数。对您的副本构造函数执行此操作不是一个好主意,因为它将破坏std :: vector的内部重新分配算法。

摘要:您将无法使用std :: vector做到这一点。


呃...

尝试方法:

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

这将使您能够为x项保留足够的内存,而无需初始化任何项(您的向量仍然为空)。因此,只有经过x才可以重新分配。

第二点是向量不会将值初始化为零。您是否正在调试中测试代码?

在g ++上验证后,以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#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 <<"
"
;
   }

   return 0 ;
}

给出以下结果:

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

您看到的初始化可能是工件。

[编辑]关于调整大小的评论之后,我修改了代码以添加调整大小行。调整大小有效地调用了vector内部对象的默认构造函数,但是如果默认构造函数不执行任何操作,则不会初始化任何内容...我仍然相信这是一个伪像(我设法第一次将整个向量与以下代码:

1
2
3
aMyStruct.push_back(MyStruct()) ;
aMyStruct.push_back(MyStruct()) ;
aMyStruct.push_back(MyStruct()) ;

所以...
:-/

[编辑2]就像Arkadiy已经提供的那样,解决方案是使用带有所需参数的内联构造函数。就像是

1
2
3
4
5
struct MyStruct
{
   MyStruct(int p_d1, int p_d2) : d1(p_d1), d2(p_d2) {}
   int d1, d2 ;
} ;

这可能会内联到您的代码中。

但是无论如何,您都应该使用探查器来研究代码,以确保这段代码是应用程序的瓶颈。


您可以在元素类型周围使用包装器类型,并使用不执行任何操作的默认构造函数。例如。:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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.
};

使用方法:

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

如果您确实坚持要对元素进行未初始化,并牺牲了诸如front(),back(),push_back()之类的方法,请使用numeric中的boost向量。它甚至允许您在调用resize()时不保留现有元素。


从您的代码看来,您似乎有一个结构向量,每个结构都包含2个整数。您可以改用2个int向量吗?然后

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

现在,您无需为每次复制结构付费。


从您的评论到其他海报,您似乎都剩下malloc()和朋友了。向量不会让您拥有未构造的元素。


使用std :: vector :: reserve()方法。它不会调整向量的大小,但是会分配空间。


我会做类似的事情:

1
2
3
4
5
6
7
8
9
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]));
  }
}

您需要为memberVector中存储的类型定义一个ctor,但这是一个很小的开销,因为它将为您提供两全其美的优势。不会进行不必要的初始化,并且在循环期间不会发生任何重新分配。


我不认为STL是您的答案。您将需要使用realloc()推出自己的解决方案。您必须存储一个指针以及元素的大小或数量,并使用该指针查找在realloc()之后开始添加元素的位置。

1
2
3
4
5
6
7
8
9
10
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;
}

结构本身是否需要在连续的内存中,还是可以避免使用struct *向量?

向量会复制您添加的内容,因此使用指针向量而非对象是提高性能的一种方法。


推荐阅读

    linux系统初始化命令?

    linux系统初始化命令?,系统,基础,工作,服务,信息,命令,软件,网络,密码,发行,l

    linux命令循环导入?

    linux命令循环导入?,服务,系统,密码,数据,增长,名字,数字,官网,地址,文件,mys

    linux编辑循环命令?

    linux编辑循环命令?,代码,增长,工具,基础,灵活,状态,连续,系统,数字,数据,lin

    linux循环命令大全?

    linux循环命令大全?,工作,地址,系统,命令,工具,代码,目录,连续,环境,管理,Lin

    循环执行linux命令?

    循环执行linux命令?,工具,系统,名称,代码,第一,环境,位置,数字,脚本,变量,Lin

    linux命令连续执行?

    linux命令连续执行?,连续,通信,工具,数据,代码,命令,设备,系统,发行,情况,如

    linux循环复制命令?

    linux循环复制命令?,系统,文件,命令,目录,地址,源文件,文件夹,目标,文件名,

    linux初始化硬盘命令?

    linux初始化硬盘命令?,系统,工作,设备,工具,信息,基础,地址,电脑,命令,数据,l

    linux连续输入命令?

    linux连续输入命令?,系统,工作,地址,连续,命令,信息,电脑,基础,标准,状态,lin

    linux命令行写循环?

    linux命令行写循环?,工作,系统,地址,命令,情况,定期,基础,连续,信息,文件,Lin

    linux循环命令脚本?

    linux循环命令脚本?,代码,系统,增长,工具,官网,项目,流程,数据,数字,底部,lin

    linux跳出死循环命令?

    linux跳出死循环命令?,系统,地址,数字,代码,官方网站,网络,工作,工具,信息,

    linux循环语句命令?

    linux循环语句命令?,地方,增长,数字,语句,流程,名称,工具,代码,数据,条件,Lin

    dolinux循环命令?

    dolinux循环命令?,系统,工具,时间,信息,增长,代码,对比,数字,官网,定期,如何

    Python循环应该怎么写?

    Python循环应该怎么写?,代码,项目,数据,名字,分析,异常,基础,培训,面临,情

    Python基础之循环

    Python基础之循环,基础,评论,培训,元素,条件,函数,上限,序列,语句,时候,循环