Я хочу использовать vector<char>
в качестве буфера. Интерфейс идеально подходит для моих нужд, но существует ограничение производительности при изменении его размера за текущий размер, поскольку память инициализируется. Мне не нужна инициализация, так как данные будут перезаписаны в любом случае некоторыми сторонними функциями C. Есть ли способ или конкретный распределитель, чтобы избежать шага инициализации? Обратите внимание, что я хочу использовать resize()
, а не другие трюки, такие как reserve()
и capacity()
, потому что мне нужно size()
всегда представлять значимый размер моего "буфера" в любой момент, а capacity()
может быть больше его размера после resize()
, поэтому, опять же, я не могу полагаться на capacity()
в качестве значимой информации для моего приложения. Furthemore, (новый) размер вектора никогда не известен заранее, поэтому я не могу использовать std::array
. Если вектор не может быть настроен таким образом, я хотел бы знать, какой контейнер или распределитель я мог бы использовать вместо vector<char, std::alloc>
. Единственное требование состоит в том, что альтернатива вектору должна быть в лучшем случае основана на STL или Boost. У меня есть доступ к С++ 11.
Используя вектор <char> в качестве буфера без его инициализации при изменении размера()
Ответ 1
В стандартной библиотеке нет ничего, что отвечало бы вашим требованиям, и я ничего не знаю об этом.
Есть три разумных варианта, о которых я могу думать:
- Придерживайтесь
std::vector
, оставляйте комментарий в коде и возвращайтесь к нему, если это когда-либо вызывает узкое место в вашем приложении. - Используйте собственный распределитель с пустыми методами
construct
/destroy
- и надейтесь, что ваш оптимизатор будет достаточно умным, чтобы удалить любые вызовы. - Создайте оболочку вокруг динамически распределенного массива, реализуя только минимальную функциональность, которая вам нужна.
Ответ 2
Это известная проблема, что инициализация не может быть отключена даже явно для std::vector
.
Люди обычно реализуют свой собственный pod_vector<>
, который не выполняет никакой инициализации элементов.
Другой способ - создать тип, совместимый с макетом с char, конструктор которого ничего не делает:
struct NoInitChar
{
char value;
NoInitChar() noexcept {
// do nothing
static_assert(sizeof *this == sizeof value, "invalid size");
static_assert(__alignof *this == __alignof value, "invalid alignment");
}
};
int main() {
std::vector<NoInitChar> v;
v.resize(10); // calls NoInitChar() which does not initialize
// Look ma, no reinterpret_cast<>!
char* beg = &v.front().value;
char* end = beg + v.size();
}
Ответ 3
Инкапсулируйте его.
Инициализируйте его до максимального размера (не резерв).
Сохраняйте ссылку на итератор, представляющий конец реального размера, как вы выразились.
Используйте begin
и real end
вместо end
для ваших алгоритмов.
Ответ 4
Как альтернативное решение, которое работает с векторами разных типов:
template<typename V>
void resize(V& v, size_t newSize)
{
struct vt { typename V::value_type v; vt() {}};
static_assert(sizeof(vt[10]) == sizeof(typename V::value_type[10]), "alignment error");
typedef std::vector<vt, typename std::allocator_traits<typename V::allocator_type>::template rebind_alloc<vt>> V2;
reinterpret_cast<V2&>(v).resize(newSize);
}
И тогда вы можете:
std::vector<char> v;
resize(v, 1000); // instead of v.resize(1000);
Скорее всего, это UB, хотя он работает для меня должным образом в тех случаях, когда мне важнее производительность. Разница в сгенерированной сборке, производимой clang:
test():
push rbx
mov edi, 1000
call operator new(unsigned long)
mov rbx, rax
mov edx, 1000
mov rdi, rax
xor esi, esi
call memset
mov rdi, rbx
pop rbx
jmp operator delete(void*)
test_noinit():
push rax
mov edi, 1000
call operator new(unsigned long)
mov rdi, rax
pop rax
jmp operator delete(void*)