С помощью векторов можно предположить, что элементы хранятся смежно в памяти, позволяя использовать диапазон [& 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).