*不*初始化其成员的C++向量?

Meh*_*dad 45 c++ stl vector

我正在为一段返回大型数组的C代码制作一个C++包装器,所以我试图将数据返回到vector<unsigned char>.

现在问题是,数据大小为兆字节,并且vector不必要地初始化其存储,这实际上是将我的速度降低了一半.

我该如何防止这种情况?

或者,如果不可能 - 是否有其他STL容器可以避免这种不必要的工作?或者我最终必须制作自己的容器?

(预C++ 11)

注意:

我将矢量作为输出缓冲区传递.我不是从其他地方复制数据.
它是这样的:

vector<unsigned char> buf(size);   // Why initialize??
GetMyDataFromC(&buf[0], buf.size());
Run Code Online (Sandbox Code Playgroud)

bam*_*s53 55

对于没有显式初始化任何内容的用户提供的默认构造函数的结构的默认值和值初始化,不对未签名的char成员执行初始化:

struct uninitialized_char {
    unsigned char m;
    uninitialized_char() {}
};

// just to be safe
static_assert(1 == sizeof(uninitialized_char), "");

std::vector<uninitialized_char> v(4 * (1<<20));

GetMyDataFromC(reinterpret_cast<unsigned char*>(&v[0]), v.size());
Run Code Online (Sandbox Code Playgroud)

我认为在严格的别名规则下这甚至是合法的.

当我比较了建设时间v与一个vector<unsigned char>我〜8微秒VS〜12毫秒.速度提高了1000多倍.编译器使用libc ++和flags进行了扼流3.2:-std=c++11 -Os -fcatch-undefined-behavior -ftrapv -pedantic -Weverything -Wno-c++98-compat -Wno-c++98-compat-pedantic -Wno-missing-prototypes

C++ 11有一个未初始化存储的帮助器,std :: aligned_storage.虽然它需要编译时间大小.


这是一个增加的例子,用于比较总使用量(以纳秒为单位的时间):

VERSION = 1(vector<unsigned char>):

clang++ -std=c++14 -stdlib=libc++ main.cpp -DVERSION=1 -ftrapv -Weverything -Wno-c++98-compat -Wno-sign-conversion -Wno-sign-compare -Os && ./a.out

initialization+first use: 16,425,554
array initialization: 12,228,039
first use: 4,197,515
second use: 4,404,043
Run Code Online (Sandbox Code Playgroud)

版本= 2(vector<uninitialized_char>):

clang++ -std=c++14 -stdlib=libc++ main.cpp -DVERSION=2 -ftrapv -Weverything -Wno-c++98-compat -Wno-sign-conversion -Wno-sign-compare -Os && ./a.out

initialization+first use: 7,523,216
array initialization: 12,782
first use: 7,510,434
second use: 4,155,241
Run Code Online (Sandbox Code Playgroud)


#include <iostream>
#include <chrono>
#include <vector>

struct uninitialized_char {
  unsigned char c;
  uninitialized_char() {}
};

void foo(unsigned char *c, int size) {
  for (int i = 0; i < size; ++i) {
    c[i] = '\0';
  }
}

int main() {
  auto start = std::chrono::steady_clock::now();

#if VERSION==1
  using element_type = unsigned char;
#elif VERSION==2
  using element_type = uninitialized_char;
#endif

  std::vector<element_type> v(4 * (1<<20));

  auto end = std::chrono::steady_clock::now();

  foo(reinterpret_cast<unsigned char*>(v.data()), v.size());

  auto end2 = std::chrono::steady_clock::now();

  foo(reinterpret_cast<unsigned char*>(v.data()), v.size());

  auto end3 = std::chrono::steady_clock::now();

  std::cout.imbue(std::locale(""));
  std::cout << "initialization+first use: " << std::chrono::nanoseconds(end2-start).count() << '\n';
  std::cout << "array initialization: " << std::chrono::nanoseconds(end-start).count() << '\n';
  std::cout << "first use: " << std::chrono::nanoseconds(end2-end).count() << '\n';
  std::cout << "second use: " << std::chrono::nanoseconds(end3-end2).count() << '\n';
}
Run Code Online (Sandbox Code Playgroud)

我正在使用clang svn-3.6.0 r218006

  • @Mehrdad未定义的行为意味着更多.UB意味着标准对UB之前或之后的程序的任何部分都没有要求.程序不能避免部分实现定义,但这并不会使它们不像UB那样受标准约束. (4认同)
  • @Mehrdad:我希望你能正确理解这个问题,但是你怎么说它会引起误解:取消引用一个无效的指针是未定义的,`char`是签名还是无符号是实现定义的; 你怎么能说那两个是_synonymous_? (4认同)
  • +1因为这太棒了.唯一的麻烦是它需要使用`reinterpret_cast`进行一些黑客攻击才能使界面成为标准(并希望获得最佳:P),但我认为它非常棒!:d (3认同)
  • "实现定义"是指特定的构造; "未定义"是指_整个程序_.即使您没有指定实现,它们也不会接近同义词.(对于UB来说,这是一个非常不被重视的观点,即使是那些认为他们了解UB的人.) (3认同)
  • @Mehrdad你似乎只是在口语中使用"未定义行为"一词.C++标准赋予这些术语特殊的意义,不会互换使用它们.例如,即使实现定义了解除引用无效指针的效果,结果仍然是_undefined-behavior_,因为该术语在_C++标准[defns.undefined] 1.3.24_中定义. (2认同)

Nic*_*las 8

对不起,没办法避免它.

C++ 11添加了一个只占用一个大小的构造函数,但即便如此,它也会对数据进行初始化.

最好的办法是在堆上分配一个数组,将其粘贴在unique_ptr(如果可用)中,然后从那里使用它.

如果你愿意,正如你所说的那样,"黑客攻击STL",你总是可以从中获取一份EASTL.它是某些STL容器的变体,允许更多受限的内存条件.正确实现你想要做的是给它的构造函数一个特殊的值,意思是"默认初始化成员",对于POD类型来说意味着什么都不做初始化内存.当然,这需要使用一些模板元编程来检测它是否是POD类型.

  • @Mehrdad:如果你的代码足够简单并且数组的生命周期很短,那么只需动态分配一个并继续.数组本身并不安全,只是在大型项目中面对可能的异常时,很难正确管理动态分配对象的释放. (3认同)
  • @chris:你只能移动彼此兼容的东西.通常,您只能将一个类实例移动到同一个类的另一个实例中,因为移动需要戳对象的内容.你肯定不能只将`Type []`移动到`std :: vector`中. (2认同)