Изменение размера С++ std::vector <char> без инициализации данных

С помощью векторов можно предположить, что элементы хранятся смежно в памяти, позволяя использовать диапазон [& vec [0], & vec [vec.capacity()) как обычный массив. Например.

vector<char> buf;
buf.reserve(N);
int M = read(fd, &buf[0], N);

Но теперь вектор не знает, что он содержит M байтов данных, добавленных извне с помощью read(). Я знаю, что vector:: resize() устанавливает размер, но он также очищает данные, поэтому его нельзя использовать для обновления размера после вызова read().

Существует ли тривиальный способ чтения данных непосредственно в векторы и обновления размера после? Да, я знаю об очевидных обходных решениях, таких как использование небольшого массива в качестве временного буфера чтения и использование vector:: insert() для добавления этого конца в конец вектора:

char tmp[N];
int M = read(fd, tmp, N);
buf.insert(buf.end(), tmp, tmp + M)

Это работает (и это то, что я делаю сегодня), но меня просто беспокоит, что там есть дополнительная операция копирования, которая не потребовалась бы, если бы я мог поместить данные непосредственно в вектор.

Итак, есть ли простой способ изменить размер вектора, когда данные были добавлены извне?

Ответ 1

vector<char> buf;
buf.reserve(N);
int M = read(fd, &buf[0], N);

Этот фрагмент кода вызывает поведение undefined. Вы не можете писать за пределами элементов size(), даже если вы зарезервировали пробел.

Правильный код выглядит так:

vector<char> buf;
buf.resize(N);
int M = read(fd, &buf[0], N);
buf.resize(M);


PS. Ваше утверждение "С векторами можно предположить, что элементы хранятся смежно в памяти, что позволяет использовать диапазон [&vec[0], &vec[vec.capacity()) как обычный массив", это неверно. Допустимый диапазон [&vec[0], &vec[vec.size()).

Ответ 2

Похоже, вы можете делать то, что хотите на С++ 11 (хотя я сам этого не пробовал). Вам нужно будет определить пользовательский распределитель для вектора, затем используйте emplace_back().

Сначала определите

struct do_not_initialize_tag {};

Затем определите свой распределитель с помощью этой функции-члена:

class my_allocator {
    void construct(char* c, do_not_initialize_tag) const {
        // do nothing
    }

    // details omitted
    // ...
}

Теперь вы можете добавлять элементы в массив без их инициализации:

std::vector<char, my_allocator> buf;
buf.reserve(N);
for (int i = 0; i != N; ++i)
    buf.emplace_back(do_not_initialize_tag());
int M = read(fd, buf.data(), N);
buf.resize(M);

Эффективность этого зависит от оптимизатора компилятора. Например, цикл может увеличивать размерную переменную члена N раз.

Ответ 3

Запись в и после size() -го элемента - это поведение undefined.

Следующий пример копирует весь файл в вектор в С++ (не нужно знать размер файла и не нужно предварительно распределять память в векторе):

#include <algorithm>
#include <fstream>
#include <iterator>
#include <vector>

int main()
{
    typedef std::istream_iterator<char> istream_iterator;
    std::ifstream file("example.txt");
    std::vector<char> input;

    file >> std::noskipws;
    std::copy( istream_iterator(file), 
               istream_iterator(),
               std::back_inserter(input));
}

Ответ 4

Ваш фрагмент программы вошел в область поведения undefined.

когда buf.empty() истинно, buf[0] имеет поведение undefined, и поэтому &buf[0] также undefined.

Этот фрагмент, вероятно, делает то, что вы хотите.

vector<char> buf;
buf.resize(N); // preallocate space
int M = read(fd, &buf[0], N);
buf.resize(M); // disallow access to the remainder

Ответ 5

Другой, более новый, вопрос, дубликат этого, имеет ответ, который похож на то, что здесь задается. Здесь его копия (v3) для быстрой справки:

Известно, что инициализация не может быть отключена даже явно для std::vector.

Люди обычно реализуют свои собственные pod_vector<>, которые не выполняют любая инициализация элементов.

Другой способ - создать тип, совместимый с макетами с char, конструктор которого ничего не делает:

struct NoInitChar
{
    char value;
    NoInitChar() {
        // 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();
}

Ответ 6

Возможно, вы захотите попытаться подклассифицировать векторный класс и реализовать метод, который устанавливает размер, напрямую обращаясь к переменным-членам: В GNU С++ это выполняется следующим фрагментом кода:

template <typename T, typename A = mmap_allocator<T> >
class mmappable_vector: public std::vector<T, A> {
public:
    void map_into_memory(const size_t n)
    {
        std::vector<T,A>::reserve(n);
#ifdef __GNUC__
        std::vector<T,A>::_M_impl._M_finish = std::vector<T,A>::_M_impl._M_end_of_storage;
#else
#error "Not GNU C++, please either implement me or use GCC"
#endif
    }
};

и используйте этот класс (вам придется добавить некоторые конструкторы) istead stl:: vector. Однако, если вы не обрабатываете файлы размером более 1 ГБ, это не стоит усилий.

Если вы это сделаете, вы можете попробовать попробовать mmap_allocator (еще бета-версия, доступная с github.com/johannesthoma/mmap_allocator).