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.