С++, который * не инициализирует свои члены?

Я создаю С++-оболочку для куска кода C, который возвращает большой массив, и поэтому я попытался вернуть данные в vector<unsigned char>.

Теперь проблема заключается в том, что данные имеют порядок мегабайт, а vector неоправданно инициализирует его хранилище, что существенно сокращает мою скорость наполовину.

Как это предотвратить?

Или, если это невозможно, есть ли какой-нибудь другой контейнер STL, который бы избегал такой ненужной работы? Или я должен создать свой собственный контейнер?

(Pre-С++, 11)

Примечание:

Я передаю вектор в качестве моего выходного буфера. Я не копирует данные из других источников.
Это что-то вроде:

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

Ответ 1

Для инициализации по умолчанию и значения для структур с предоставленными пользователем конструкторами по умолчанию, которые явно не инициализируют ничего, никакая инициализация не выполняется для неподписанных 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());

Я думаю, что это даже законно по строгим правилам псевдонимов.

Когда я сравнивал время построения v против a vector<unsigned char>, я получил ~ 8 мкс против ~ 12 мс. Более 1000 раз быстрее. Компилятор был clang 3.2 с libС++ и flags: -std=c++11 -Os -fcatch-undefined-behavior -ftrapv -pedantic -Weverything -Wno-c++98-compat -Wno-c++98-compat-pedantic -Wno-missing-prototypes

В С++ 11 есть помощник для неинициализированного хранилища, std:: aligned_storage. Хотя для этого требуется размер времени компиляции.


Здесь добавлен пример сравнения общего использования (раз в наносекундах):

ВЕРСИЯ = 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

ВЕРСИЯ = 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


#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';
}

Я использую clang svn-3.6.0 r218006

Ответ 2

Извините, нет способа избежать этого.

С++ 11 добавляет конструктор, который принимает только размер, но даже это значение будет инициализировать данные.

Лучше всего просто выделить массив в куче, вставить его в unique_ptr (где доступно) и использовать его оттуда.

Если вы хотите, как вы говорите, "взломать STL", вы всегда можете получить копию EASTL для работы. Это вариация некоторых контейнеров STL, которая позволяет использовать более ограниченные условия памяти. Правильная реализация того, что вы пытаетесь сделать, - это дать конструктору специальное значение, означающее "инициализировать элементы по умолчанию", что для типов POD означает ничего не делать для инициализации памяти. Для этого требуется использование некоторых метапрограмм шаблонов, чтобы определить, является ли это тип POD, конечно.

Ответ 3

1 Похоже, что использование std::vector не является необходимым для вашей ситуации. Вы хотите, чтобы какой-то объект мог управлять некоторой необработанной памятью для вас. Этого можно легко достичь с помощью

std::unique_ptr<void, void(*)(void*)> p(std::malloc(n), std::free);

2 Если вы действительно хотите использовать std::vector<>, вы можете использовать трюк, описанный здесь здесь.

Ответ 4

Оптимальное решение - просто изменить распределитель, чтобы ничего не делать для нулевых аргументов construct. Это означает, что базовый тип один и тот же, что уклоняется от любых неприятных ошибок reinterpret_casting и потенциальных нарушений псевдонимов и может неинтрузивно uninitialize любого типа.

template<typename T> struct no_initialize : std::allocator<T> {
    void construct(T* p) {}
    template<typename... Args> void construct(T* p, Args&&... args) {
        new (p) T(std::forward<Args>(args)...);
    }
};

Ответ 5

Как насчет использования vector.reserve(), чтобы распределять память, но не инициализировать ее?