Используя вектор <char> в качестве буфера без его инициализации при изменении размера()

Я хочу использовать vector<char> в качестве буфера. Интерфейс идеально подходит для моих нужд, но существует ограничение производительности при изменении его размера за текущий размер, поскольку память инициализируется. Мне не нужна инициализация, так как данные будут перезаписаны в любом случае некоторыми сторонними функциями C. Есть ли способ или конкретный распределитель, чтобы избежать шага инициализации? Обратите внимание, что я хочу использовать resize(), а не другие трюки, такие как reserve() и capacity(), потому что мне нужно size() всегда представлять значимый размер моего "буфера" в любой момент, а capacity() может быть больше его размера после resize(), поэтому, опять же, я не могу полагаться на capacity() в качестве значимой информации для моего приложения. Furthemore, (новый) размер вектора никогда не известен заранее, поэтому я не могу использовать std::array. Если вектор не может быть настроен таким образом, я хотел бы знать, какой контейнер или распределитель я мог бы использовать вместо vector<char, std::alloc>. Единственное требование состоит в том, что альтернатива вектору должна быть в лучшем случае основана на STL или Boost. У меня есть доступ к С++ 11.

Ответ 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*)