Using vector<char> as a buffer without initializing it on resize()
Solution 1:
It is a known issue that initialization can not be turned off even explicitly for std::vector
.
People normally implement their own pod_vector<>
that does not do any initialization of the elements.
Another way is to create a type which is layout-compatible with char, whose constructor does nothing:
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();
}
Solution 2:
There's nothing in the standard library that meets your requirements, and nothing I know of in boost either.
There are three reasonable options I can think of:
- Stick with
std::vector
for now, leave a comment in the code and come back to it if this ever causes a bottleneck in your application. - Use a custom allocator with empty
construct
/destroy
methods - and hope your optimiser will be smart enough to remove any calls to them. - Create a wrapper around a a dynamically allocated array, implementing only the minimal functionality that you require.
Solution 3:
As an alternative solution that works with vectors of different pod types:
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);
}
And then you can:
std::vector<char> v;
resize(v, 1000); // instead of v.resize(1000);
This is most likely UB, even though it works properly for me for cases where I care more about performance. Difference in generated assembly as produced by 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*)
Solution 4:
Encapsulate it.
Initialise it to the maximum size (not reserve).
Keep a reference to the iterator representing the end of the real size, as you put it.
Use begin
and real end
, instead of end
, for your algorithms.