Есть ли более эффективный способ установить std::vector из потока?

В настоящее время я установил значение std::vector<char> из std::ostringstream следующим образом:

void
foo(std::vector<char> &data, std::stringstream &stream) {
  data = std::vector<char>(stream.str().begin(), stream.str().end());
}

Мне интересно, есть ли более эффективный способ сделать это с помощью STL в C++ или метод, который я здесь привожу, считается подходящим? Не лучше ли мне вместо этого использовать std::stringstream?


person WilliamKF    schedule 30.05.2012    source источник
comment
Я не уверен, насколько это эффективно, но это неверно. Два вызова .str() возвращают разные объекты.   -  person Robᵩ    schedule 30.05.2012
comment
Спасибо, что указали на эту ошибку, я думал, что получаю ссылку, а не копию из str().   -  person WilliamKF    schedule 31.05.2012


Ответы (4)


Ваш метод вызывает неопределенное поведение. stream.str() возвращает строку по значению, также известную как временная строка. Вы берете итератор begin одного временного объекта и итератор end другого, создавая недопустимый диапазон.

Одним из способов преобразования потока в контейнер является использование общего интерфейса итератора:

#include <iostream>
#include <sstream>
#include <vector>
#include <algorithm>
#include <iterator>

int main(){
  std::stringstream src("....");
  std::vector<char> dest;
  // for a bit of efficiency
  std::streampos beg = src.tellg();
  src.seekg(0, std::ios_base::end);
  std::streampos end = src.tellg();
  src.seekg(0, std::ios_base::beg);
  dest.reserve(end - beg);

  dest.assign(std::istreambuf_iterator<char>(src), std::istreambuf_iterator<char>());

  std::copy(dest.begin(), dest.end(), std::ostream_iterator<char>(std::cout));
}

Живой пример на Ideone.

Другой метод заключается в кэшировании возвращаемого объекта std::string:

std::string const& s = stream.str();
data.reserve(s.size());
data.assign(s.begin(), s.end());
person Xeo    schedule 30.05.2012
comment
Почему есть два вызова std::copy()? Мне они кажутся излишними. - person WilliamKF; 31.05.2012
comment
@WilliamKF: первый (теперь замененный на .assign) должен был вставляться в вектор, второй выводился в std::cout (слишком ленив для рукописного цикла). - person Xeo; 31.05.2012
comment
Я полагаю, слишком много надеяться, что src.rdbuf()->in_avail() даст правильный размер, не возясь с seek? Я плохо разбираюсь в потоковых библиотеках. - person Steve Jessop; 31.05.2012
comment
@Steve: Кажется, ты лучше меня, потому что я даже не помнил эту функцию до сих пор. Однако понятия не имею, будет ли он работать так же. - person Xeo; 31.05.2012

Как указано в комментариях, ваш код неверен из-за двух вызовов str(). Чтобы повысить эффективность, вы можете не создавать временный vector, например:

void foo(std::vector<char> &data, std::stringstream &stream) {
    const std::string& str = stream.str();
    data.assign( str.begin(), str.end() );
}

Вы также можете избежать std::string, используя std::istreambuf_iterators:

void foo(std::vector<char> &data, std::stringstream &stream) {
    data.assign(
        std::istreambuf_iterator<char>( stream ), std::istreambuf_iterator<char>()
    );
}

но, учитывая, что это итераторы ввода, vector не имеет возможности узнать, сколько данных будет назначено, и может работать немного хуже, так как не может reserve иметь достаточно места, чтобы избежать перераспределения.

person K-ballo    schedule 30.05.2012
comment
Я думаю, вы, вероятно, захотите istreambuf_iterator здесь, а не istream_iterator, иначе вы потеряете пробелы. - person ildjarn; 31.05.2012
comment
Да, за исключением того, что не указан тип персонажа. :-] - person ildjarn; 31.05.2012
comment
@ildjarn: это нужно? Ссылка, которую я проверяю, говорит, что по умолчанию используется char. Действительно, нужен. Похоже на причуду в этой конкретной ссылке. - person K-ballo; 31.05.2012
comment
В §24.6.3 говорится, что подпись template<class charT, class traits = char_traits<charT> > class istreambuf_iterator, по умолчанию нет. - person ildjarn; 31.05.2012
comment
Вы можете получить правильный размер от reserve до seekg и tellg на std::stringstream, см. мой ответ. - person Xeo; 31.05.2012
comment
О, кстати, istreambuf_iterator создаются из потоков, нет необходимости в вызове rdbuf. :) - person Xeo; 31.05.2012

Скопируйте из итератора потока в итератор обратной вставки:

std::istream src;
std::vector<char> dst;

std::copy(std::istream_iterator<char>(src), std::istream_iterator<char>(), std::back_inserter(dst));

istream_iterator использует форматированное преобразование (т. е. пропускает пробелы), так что это может быть не то, что вам нужно. Я не уверен, какова ваша цель.

person brewbuck    schedule 30.05.2012

если есть более эффективный способ

Вы можете вызвать reserve на своем data и использовать член диапазона insert непосредственно на data вместо использования копирования-назначения. Что вам нужно помнить о vectors, так это то, что каждый узел потенциально может увеличить размер (и переместить все элементы). Итак, вам лучше выделить память за один раз (если вы знаете, сколько объектов вы будете хранить — что вы знаете здесь) и использовать этот факт.

person dirkgently    schedule 30.05.2012
comment
На самом деле я предполагаю, что в большинстве реализаций будет использоваться вызов std::distance в основанном на итераторе конструктор, чтобы сделать эту логику самостоятельно. - person KillianDS; 31.05.2012
comment
@KillianDS: Вероятно, только для итераторов с произвольным доступом, иначе диапазон будет без необходимости проходиться дважды. - person ildjarn; 31.05.2012
comment
@ildjarm: и обязательно не для итераторов ввода, которые нельзя пройти дважды. - person Steve Jessop; 31.05.2012