вектор ‹unsigned char› против строки для двоичных данных

Какой контейнер С++ лучше для хранения двоичных данных и доступа к ним?

std::vector<unsigned char>

or

std::string

Один из них более эффективен, чем другой?
Является ли один из них более «правильным»?


person kalaxy    schedule 12.10.2009    source источник
comment
Посмотрите этот пост об использовании char и unsigned char для двоичных данных: stackoverflow.com/questions/277655/   -  person fnieto - Fernando Nieto    schedule 14.10.2009


Ответы (9)


Вы должны предпочесть std::vector вместо std::string. В общих случаях оба решения могут быть почти эквивалентны, но std::strings разработаны специально для строк и манипуляций со строками, и это не ваше предполагаемое использование.

person David Rodríguez - dribeas    schedule 12.10.2009
comment
Скажем, что черты характера по умолчанию определяют, что 'a' и 'á' эквивалентны. Это плохое предположение. Смотрите ответ, который я написал как продолжение этого комментария. - person fnieto - Fernando Nieto; 13.10.2009
comment
Я перепроверил, и вы правы в том, что стандарт определяет специализацию char_traits<char>, а со стандартной специализацией присваивание, сравнение и упорядочение определяются как эквивалент встроенного типа char. - person David Rodríguez - dribeas; 13.10.2009
comment
Таким образом, с char_traits по умолчанию std::string будет сравниваться не иначе, как соответствующий std::vector? - person kalaxy; 14.10.2009
comment
@kalaxy: правильно. В любом случае, каждый класс предназначался для определенной цели, и std::vector лучше соответствует тому, что вы хотите от буфера, поэтому, если только из-за более ясного намерения (как указывает fnieto в своем ответе), я бы предпочел std::vector - person David Rodríguez - dribeas; 14.10.2009
comment
@DavidRodríguez-dribeas: я отредактировал ваш ответ, так как понимаю (из комментариев), что предыдущая версия была неверной. - person user541686; 06.07.2012

Оба варианта правильны и одинаково эффективны. Использование одного из них вместо простого массива предназначено только для облегчения управления памятью и передачи их в качестве аргумента.

Я использую вектор, потому что намерение более ясно, чем со строкой.

Изменить: стандарт C++03 не гарантирует непрерывность памяти std::basic_string. Однако с практической точки зрения коммерческих несмежных реализаций не существует. C++0x установлен на стандартизировать этот факт< /а>.

person fnieto - Fernando Nieto    schedule 12.10.2009
comment
от Sgi: класс basic_string представляет собой последовательность символов. Он содержит все обычные операции с Sequence и, кроме того, содержит стандартные строковые операции, такие как поиск и объединение. Почему это неправильно? Я согласен, что это не лучший подход (как я утверждаю в своем ответе), но он не является неправильным. - person fnieto - Fernando Nieto; 13.10.2009
comment
Таким образом, строка работает так же хорошо, как и вектор, потому что она в некотором смысле расширяет функциональность вектора, но единственная функциональность, которая мне понадобится ([] или тому подобное), содержится в обоих? (Да, я понимаю, что строка на самом деле не наследуется от вектора.) - person kalaxy; 13.10.2009
comment
Да, но концептуально это худший вариант и иметь методы, которые не могут иметь смысла для буфера. Если вам нужно только управление памятью и оператор [], зачем использовать такой сложный класс, как std::string. - person fnieto - Fernando Nieto; 14.10.2009

Является ли один более эффективным, чем другой?

Это неправильный вопрос.

Является ли одно из них более «правильным» использованием?

Это правильный вопрос.
Это зависит от ситуации. Как используются данные? Если вы собираетесь использовать данные в виде строки, такой как fashon, вам следует выбрать std::string, так как использование std::vector может запутать последующих сопровождающих. Если, с другой стороны, большая часть манипуляций с данными выглядит как простая математика или вектор, то более подходящим является std::vector.

person Martin York    schedule 12.10.2009

Долгое время я соглашался с большинством ответов здесь. Однако только сегодня меня осенило, почему было бы разумнее использовать std::string вместо std::vector<unsigned char>.

Большинство согласны с тем, что использование любого из них будет работать нормально. Но часто файловые данные могут быть в текстовом формате (что более распространено сейчас, когда XML стал мейнстримом). Это упрощает просмотр в отладчике, когда это становится уместным (и эти отладчики все равно часто позволяют вам перемещаться по байтам строки). Но что более важно, многие существующие функции, которые можно использовать со строкой, можно легко использовать с файловыми/двоичными данными. Я обнаружил, что пишу несколько функций для обработки как строк, так и массивов байтов, и понял, насколько все это бессмысленно.

person Mike Weir    schedule 28.11.2017

Это комментарий к ответу dribeas. Я пишу это как ответ, чтобы иметь возможность форматировать код.

Это функция сравнения char_traits, и поведение вполне нормальное:

static bool
lt(const char_type& __c1, const char_type& __c2)
{ return __c1 < __c2; }

template<typename _CharT>
int
char_traits<_CharT>::
compare(const char_type* __s1, const char_type* __s2, std::size_t __n)
{
  for (std::size_t __i = 0; __i < __n; ++__i)
if (lt(__s1[__i], __s2[__i]))
  return -1;
else if (lt(__s2[__i], __s1[__i]))
  return 1;
  return 0;
}
person fnieto - Fernando Nieto    schedule 13.10.2009
comment
Хорошо ли это поведение определено в стандарте? - person gnud; 13.10.2009
comment
+1: @gnud: Не в общем, но fnieto прав (я только что проверил) в том, что стандарт определяет специализацию трейтов для char, где assign, eq и lt должны быть определены как встроенные операторы =, == и ‹ для типа char. - person David Rodríguez - dribeas; 13.10.2009

Что касается удобочитаемости, я предпочитаю std::vector. В этом случае std::vector должен быть контейнером по умолчанию: цель более ясна и, как уже говорилось в других ответах, в большинстве реализаций она также более эффективна.

Однако однажды я предпочел std::string std::vector. Давайте посмотрим на сигнатуры их конструкторов перемещения в C++11:

вектор (вектор&& x);

строка (string&& str) без исключений;

В этом случае мне действительно понадобился конструктор перемещения noexcept. std::string предоставляет его, а std::vector — нет.

person Arnaud    schedule 24.06.2014

Если вы просто хотите сохранить свои двоичные данные, вы можете использовать bitset, который оптимизирует выделение пространства. В противном случае выберите vector, так как он больше подходит для вашего использования.

person Jacob    schedule 12.10.2009
comment
битсет не является хорошим выбором. Как вы собираетесь вернуть данные без кастинга? Как легко прочитать байт из набора битов? Это не правильное приложение для битсета. - person Brian Neal; 13.10.2009
comment
Следовательно, если вы просто хотите сохранить свои двоичные данные. Это важно в некоторых процессах с интенсивным использованием памяти, например. при работе с двоичными изображениями вы захотите временно сохранить их, а затем повторно использовать позже. - person Jacob; 13.10.2009
comment
Как часто вы на самом деле просто храните данные? Если бы я собирался сохранить его, я бы использовал файл или просто массив или вектор. Какие преимущества имеет битсет для хранения? Как вы вообще получаете свои двоичные данные в набор битов? Для этой цели у Bitset действительно паршивые конструкторы. Вы действительно пытались это сделать? У Bitset есть конструктор по умолчанию, конструктор, принимающий unsigned long, и конструктор, принимающий строку. Не очень удобно для этой цели. - person Brian Neal; 13.10.2009
comment
Хранение его в массиве или векторе лишило бы смысла хранение, поскольку мы используем набор битов для его оптимизированного распределения битов. Передача строки битов не такая уж и сложная. Что касается приложений, бинарные изображения — это одно из них: RGB 1024x768 занимает 2,25 МБ и хранится в виде символов — представьте себе хранение небольшого пакета кадров (что не нереально). Кроме того, чтение и запись в файлы намного медленнее, чем их временное хранение в виде набора битов. Кроме того, я упомянул, что если хранение не было основной мотивацией, vector лучше. - person Jacob; 13.10.2009
comment
Bitset не оптимизирован для хранения битов. На самом деле стандарт не дает никаких гарантий относительно того, как на самом деле хранятся биты. Bitset используется, когда нужен, что еще, набор битов, как, например, манипулирование флагами. Подскажите, пожалуйста, как вы собираетесь хранить бинарное изображение размером 2,25 МБ в битовом наборе. Нет ничего более оптимизированного для распределения пространства, чем массив беззнаковых символов. - person Brian Neal; 13.10.2009
comment
Прочтите строку об оптимизации распределения пространства: cplusplus.com/reference/stl/bitset. - person Jacob; 13.10.2009
comment
Джейкоб, это глупо. Вы утверждаете, что набор битов полезен для хранения двоичных данных. Это абсурд. Bitset не является контейнером, и у него нет подходящих конструкторов для инициализации из необработанных данных, в отличие от вектора или строки. Вы серьезно говорите мне, что создадите строку ASCII 1 и 0 из 2,25 МБ двоичных данных, чтобы построить набор бит??? Это довольно большая строка. Подумай об этом. Bitset не предназначался для этой цели. Стандарт C++ даже не указывает, как набор битов хранит данные внутри, в отличие от вектора, непрерывность которого гарантируется стандартом. - person Brian Neal; 13.10.2009
comment
В C++ нет более компактного способа хранения данных в памяти, чем массив беззнаковых символов. Стандарт гарантирует, что вы можете обращаться с памятью внутри vector‹unsigned char› как с непрерывным массивом. Вы не можете (переносимо) сделать это с битовым набором. Вы также не можете (переносимо) memcpy необработанные данные в набор битов. - person Brian Neal; 13.10.2009
comment
bitset эффективен при хранении двоичных данных — я никогда не говорил, что bitset является контейнером STL. И создать эту довольно большую строку (которая будет использовать unsigned char, кстати) тривиально. Кроме того, все, что я видел до сих пор (пример кода в моем компиляторе, поиск в Google и эффективный STL (стр. 70)) указывает на то, что набор битов действительно хранит двоичные данные эффективно. И да, есть лучший способ хранения двоичных данных, и это bitset — вы пробовали его на своем компиляторе? Всего две строчки кода. - person Jacob; 13.10.2009
comment
Чтобы инициализировать набор битов размером 2,25 МБ, вам потребуется строка размером 10 МБ; каждый символ в строке представляет только один бит в наборе битов. Кроме того, вам нужно знать, сколько бит вам потребуется во время компиляции. Есть только два способа массового извлечения содержимого битового набора: to_ulong бесполезен, если у вас больше битов, чем помещается в long, и to_string возвращает строку нулей и единиц, которую нельзя легко использовать в любом другом типе данных. Итак, да, если все, что вы хотите сделать, это сохранить заданный объем данных, битовый набор может быть в порядке. Если вы хотите вернуть данные или если размер не определен, то это плохой выбор. - person Rob Kennedy; 13.10.2009
comment
Согласен, если размер неизвестен, это паршиво, но вернуть данные — это not, поскольку это то же самое, что и сохранение данных, вы можете использовать bitset::to_string. И да, вам нужна строка размером 10 МБ — в этом весь смысл использования битового набора. Предположим, у вас есть массив битов, который вы получили в виде беззнаковых символов, возможно, после какой-то логической операции, и он составляет 10 МБ, и вы хотите сохранить его в памяти - что вы делаете? bitset! - person Jacob; 13.10.2009
comment
Ха-ха, вы продолжаете возиться со своей 10-мегабайтной строкой, а я буду использовать свой 2-мегабайтный вектор‹unsigned char›. Я до сих пор понятия не имею, почему вы считаете, что набор битов хорош для хранения данных. Чем он лучше вектора? И что, черт возьми, вы должны делать с ним, пока он в битсете? И да, я пытался использовать набор битов для двоичных данных. На самом деле я написал свою собственную реализацию набора битов и дал ей конструкторы и методы доступа для получения и возврата необработанных данных для встроенных систем. Но мне это нужно, потому что я использовал его по назначению, как набор битовых флагов, а не хранилище. - person Brian Neal; 14.10.2009
comment
Тот факт, что набор битов не предоставляет конструкторы (начало, конец) и методы доступа к необработанным данным, делает его абсолютно ужасным для хранения данных. Ваш единственный вход или выход для большого количества битов - это строка? Вы также не можете сказать, что он оптимизирован для хранения. Как я уже говорил несколько раз, стандарт не гарантирует, как набор битов должен хранить данные, в отличие от вектора. Насколько вам известно, ваш набор битов может хранить 1 бит в каждом байте для скорости. Я не знаю ни одной реализации, которая на самом деле делает это, но именно поэтому вы не можете рассчитывать на это или переносимое memcpy. P.S. Не полагайтесь на cplusplus.com во всем. - person Brian Neal; 14.10.2009
comment
Я не думаю, что вы понимаете, что я говорю. Ваш 2 МБ вектор‹unsigned char›, который должен представлять 2 Мбита, может быть более эффективно сохранен в большинстве реализаций (не могли бы вы указать реализацию, которая работает так плохо? Я не могу найти ее!) с использованием набора битов . Как? Вы бросаете его в конструктор и пуф! вы получаете набор битов, который сохранил ваши данные, возможно, в 8 раз. Кроме того, все, что я сказал неоднократно, это хранилище. Ничего о аксессорах и т.д. и т.п. - person Jacob; 14.10.2009
comment
@Jacob: Я думаю, у тебя проблемы со связью с Брайаном. Если вы читаете необработанное изображение 1024x768@24 бит, у вас будет 2,25 МБ информации. Максимум, что набор битов может упаковать данные, — это один бит для каждого элемента, и на этом уровне он потребует ровно 2,25 МБ памяти, как вектор байтов. Bitset будет преимуществом, если каждый из ваших исходных элементов бит (в этот момент вы можете заметить, что std::vector<bool> — это специализация, оптимизированная для пространства, а не то, что комитет по стандарту доволен этим), поэтому в этот момент он выиграет. даже занимает больше памяти, чем набор битов. - person David Rodríguez - dribeas; 14.10.2009
comment
... Теперь, если ваше предполагаемое использование - тестирование флагов, использование вектора байтов будет более громоздким, поскольку потребуется извлечь каждый байт, а затем проверить каждый бит для чтения, извлечь байт, установить бит и вставить результат обратно для настройка немного. В этот момент использование набора битов или вектора‹bool› упростит пользовательский код. Но дело в том, что если элементы, с которыми вы работаете, являются не битами, а байтами, то вектор более эффективен с точки зрения процессора, чем набор битов, и не менее эффективен с точки зрения памяти. В большинстве случаев, когда люди говорят о хранении двоичных данных, они имеют в виду байты, а не биты. - person David Rodríguez - dribeas; 14.10.2009

Сравните эти 2 и выберите сами, что более конкретно для вас. Оба очень надежны, работают с алгоритмами STL ... Выбирайте сами, что более эффективно для вашей задачи.

person Davit Siradeghyan    schedule 12.10.2009

Лично я предпочитаю std::string, потому что string::data() гораздо более интуитивно понятен для меня, когда я хочу, чтобы мой двоичный буфер вернулся в C-совместимую форму. Я знаю, что векторные элементы гарантированно будут храниться непрерывно, поэтому выполнение этого в коде кажется немного тревожным.

Это стилевое решение, которое должен принять для себя отдельный разработчик или команда.

person Oleg Zhylin    schedule 12.10.2009
comment
Вы предпочитаете использовать строку для нестроковых данных? Вместо использования контейнера, предназначенного для непрерывного хранения данных любого типа? - person jalf; 13.10.2009
comment
Не будем забывать, что это вопрос стиля. С помощью любого из этих классов можно создать вполне работоспособный и совместимый со стандартами код для двоичных буферов. Я бы сказал, что вектор также не предназначен для использования в качестве двоичного буфера. Он совместим, но вам придется вернуться к алгоритмам или трюкам C, чтобы выполнить работу. Не все строковые операции безопасны, но некоторые из них весьма полезны, чтобы сделать код чище и удобнее в сопровождении. - person Oleg Zhylin; 13.10.2009
comment
Vector вполне подходит для хранения двоичных данных, например. вектор‹беззнаковый символ› v(256). Я не считаю &v[0] трюком C. - person Brian Neal; 13.10.2009
comment
Нет, с &v[0] все в порядке, как и с s.data(). Какова альтернатива вектора для строки s; s.assign(BinaryBuffer, BinaryBufferSize); ? - person Oleg Zhylin; 13.10.2009
comment
вектор‹беззнаковый символ› v; v.assign(BinaryBuffer, BinaryBuffer + BinaryBufferSize); - person Brian Neal; 13.10.2009
comment
Конечно, для этой цели у вектора есть явный конструктор: vector‹unsigned char› v(first, last); - person Brian Neal; 13.10.2009
comment
Таким образом, вы должны явно параметризовать вектор с помощью unsigned char и убедиться, что арифметика указателя работает правильно в BinaryBuffer + BinaryBufferSize. Похоже, для меня больше подводных камней, чем вариант строки. Как я сказал в начале, это явно проблема стиля. Универсального стиля не бывает. Команды или отдельные разработчики должны решить, какой вариант им больше нравится, и придерживаться его. - person Oleg Zhylin; 13.10.2009
comment
Гм, строка уже параметризована char, вы заметили? Так что введите свой вектор‹unsigned char›, если это заставляет вас чувствовать себя странно. Строка предназначена для строк символов, а не для необработанных двоичных данных. Строка - гораздо более тяжелое решение. - person Brian Neal; 14.10.2009
comment
И что вы имеете в виду, говоря, что арифметика указателей работает правильно? Vector использует идиому с двумя итераторами (начало, конец), как и остальная часть STL (и строка). Едва ли больше подводных камней, чем строка. - person Brian Neal; 14.10.2009
comment
Арифметика указателей может сыграть злую шутку, если BinaryBuffer не является (unsigned char*). Не могли бы вы уточнить, что делает строку намного более тяжелой? - person Oleg Zhylin; 14.10.2009