c ++ stringstream для вывода в строку - PullRequest
3 голосов
/ 02 ноября 2011

Я хотел бы иметь возможность:

foo(stringstream()<<"number = " << 500);

РЕДАКТИРОВАТЬ: однострочное решение имеет решающее значение, так как это для целей ведения журнала. Это будет все вокруг кода.

внутри foo выведет строку на экран или что-то в этом роде.

теперь, поскольку оператор stringstream << возвращает ostream &, подпись foo должна быть: </p>

foo(ostream& o);

но как я могу преобразовать ostream & в string? (или символ *). Также приветствуются различные подходы к достижению этого варианта использования.

Ответы [ 8 ]

6 голосов
/ 02 ноября 2011

Очевидным решением является использование dynamic_cast в foo.Но данный код все равно не будет работать.(Ваш пример скомпилируется, но он не будет делать то, что, как вы думаете, должен.) Выражение std::ostringstream() является временным, вы не можете инициализировать неконстантную ссылку с временным, а первый аргумент std::operator<<( std::ostream&, char const*)это неконстантная ссылка.(Вы можете вызывать функцию-член временно. Например, std::ostream::operator<<( void const* ). Таким образом, код будет компилироваться, но он не будет выполнять то, что вы ожидаете.

Вы можете обойти эту проблему, используя что-то вроде:

foo( std::ostringstream().flush() << "number = " << 500 );

std::ostream::flush() возвращает неконстантную ссылку, поэтому дальнейших проблем не возникает. А на вновь созданном потоке это не работает. Тем не менее, я думаю, вы согласитесь, что это не так.Это не самое элегантное или интуитивно понятное решение.

В таких случаях я обычно создаю класс-оболочку, который содержит собственный std::ostringstream и предоставляет шаблонный член operator<<который перенаправляет к содержавшемуся std::ostringstream. Ваша функция foo будет принимать ссылку const на это - или то, что я хочу сделать, это напрямую вызвать деструктор foo, так что клиентскому коду даже не придетсябеспокоиться об этом, она делает что-то вроде:

log() << "number = " << 500;

Функция log() возвращает экземпляр класса-оболочки (но см. ниже), а деструктор (последний) этого класса вызывает вашу функцию foo.

Естьодна небольшая проблема с этим.Возвращаемое значение может быть скопировано и уничтожено сразу после копирования.Что разрушит то, что я только что объяснил;фактически, поскольку std::ostringstream не копируется, он даже не будет компилироваться.Решение здесь состоит в том, чтобы поместить всю действующую логику, включая экземпляр std::ostringstream и логику деструктора, вызывающую foo, в отдельный класс реализации, иметь общедоступную оболочку, имеющую boost::shared_ptr, и пересылать.Или просто переопределите немного логики общего указателя в вашем классе:

class LogWrapper
{
    std::ostringstream* collector;
    int* useCount;
public:
    LogWrapper()
        : collector(new std::ostringstream)
        , useCount(new int(1))
    {
    }

    ~LogWrapper()
    {
        -- *useCount;
        if ( *useCount == 0 ) {
            foo( collector->str() );
            delete collector;
            delete useCount;
        }
    }

    template<typename T>
    LogWrapper& operator<<( T const& value )
    {
        (*collector) << value;
        return *this;
    }
};

Обратите внимание, что это легко расширить для поддержки необязательного ведения журнала;просто предоставьте конструктор для LogWrapper, который устанавливает collector в NULL, и проверьте это в operator<<.

РЕДАКТИРОВАНИЕ:

Мне приходит на ум еще одна вещь: вы 'Возможно, вы захотите проверить, вызывается ли деструктор в результате исключения, и не вызывать foo в этом случае.Логически, я надеюсь, что единственное исключение, которое вы можете получить, это std::bad_alloc, но всегда найдется пользователь, который напишет что-то вроде:

log() << a + b;

, где + - это определенная пользователем перегрузка, котораяброски.

3 голосов
/ 02 ноября 2011

Я бы предложил вам использовать эту служебную структуру:

struct stringbuilder
{
   std::stringstream ss;
   template<typename T>
   stringbuilder & operator << (const T &data)
   {
        ss << data;
        return *this;
   }
   operator std::string() { return ss.str(); }
};

И использовать ее как:

void f(const std::string & s );

int main() 
{
  char const *const pc = "hello";

  f(stringbuilder() << '{' << pc << '}' );

  //this is my most favorite line
  std::string s = stringbuilder() << 25  << " is greater than " << 5 ;
}

Демонстрация (с еще несколькими примерами): http://ideone.com/J995r

Подробнее в моем блоге: Создать строку на лету всего в одну строку

3 голосов
/ 02 ноября 2011

Вы можете использовать прокси-объект для этого; это немного основы, но если вы хотите использовать эту нотацию во многих местах, то это может стоить:

#include <iostream>
#include <sstream>

static void foo( std::string const &s )
{
    std::cout << s << std::endl;
}

struct StreamProxy
{
    std::stringstream stream;
    operator std::string() { return stream.str(); }
};

template <typename T>
StreamProxy &operator<<( StreamProxy &s, T v )
{
    s.stream << v;
    return s;
}

static StreamProxy make_stream()
{
    return StreamProxy();
}

int main()
{
    foo( make_stream() << "number = " << 500 );
}

Эта программа печатает

number = 500

Идея состоит в том, чтобы иметь небольшой класс-обертку, который можно косвенно преобразовать в std::string. Оператор << просто перенаправляется на содержащийся std::stringstream. Строго говоря, функция make_stream() не обязательна (вы могли бы также сказать StreamProxy(), но я подумал, что она выглядит немного лучше.

1 голос
/ 29 июня 2013

Если вы не возражаете против использования функций макросов, вы можете заставить функцию регистрации принимать const string& и использовать следующий макрос

#define build_string(expr) \
    (static_cast<ostringstream*>(&(ostringstream().flush() << expr))->str())

И предположим, что у вас foo есть подпись void foo(const string&),вам нужен только однострочный

foo(build_string("number = " << 500))

Это было вдохновлено ответом Джеймса Канзе о static_cast и stringstream.flush.Без .flush() вышеописанный метод завершается с неожиданным выводом.

Обратите внимание, что этот метод не должен пропускать память, так как временные значения, в форме указателя или нет, по-прежнему размещаются в стеке и, следовательно, уничтожаются.по возвращении.

1 голос
/ 02 ноября 2011

Пара опций, отличных от симпатичного прокси-решения, только что представленного Frerich Raabe:

  • Определение статической строковой переменной потока в заголовке, которая определяет функцию ведения журнала, и использование оператора запятойв вашем вызове функции регистрации, так что эта переменная передается вместо ostream&, возвращаемого оператором вставки потока.Вы можете использовать макрос регистрации, чтобы скрыть это безобразие.Проблема с этим решением в том, что оно немного уродливо, но это широко используемый подход к ведению журнала.

  • Не используйте ввод-вывод C ++.Вместо этого используйте решение в стиле C от varargs.Передайте строку формата в качестве первого аргумента, а оставшиеся аргументы будут целевыми для этой строки формата.Проблема с этим решением состоит в том, что даже если ваш компилятор достаточно умен, чтобы гарантировать, что printf и его кузены безопасны, компилятор, вероятно, не будет знать, что эта новая функция является частью семейства printf.Тем не менее, это также часто используемый подход.

0 голосов
/ 02 ноября 2011

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

Кроме того, при необходимости вы можете добавить поддержку манипуляторов (std::hex).

0 голосов
/ 02 ноября 2011

Это невозможно. Как следует из названия ostream, оно используется для вывода, для записи в него. Вы можете изменить параметр на stringstream&. Этот класс имеет метод str(), который возвращает std::string для вашего использования.

РЕДАКТИРОВАТЬ Я не читал проблему с operator <<, возвращающим ostream&. Поэтому я думаю, что вы не можете просто написать свои заявления в списке аргументов функций, но должны написать это раньше.

0 голосов
/ 02 ноября 2011

Так как вы все равно конвертируете в строку, почему бы и нет

void foo(const std::string& s)
{
    std::cout << "foo: " << s << std::endl;
}

...

std::stringstream ss;
ss << "number = " << 500;
foo(ss.str());
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...