stringstream временная проблема с возвратом ostream

Я создаю регистратор со следующими разделами:

// #define LOG(x) // for release mode
#define LOG(x) log(x)

log(const string& str);
log(const ostream& str);

С идеей сделать:

LOG("Test");
LOG(string("Testing") + " 123");
stringstream s;
LOG(s << "Testing" << 1 << "two" << 3);

Все это работает по назначению, но когда я это делаю:

LOG(stringstream() << "Testing" << 1 << "two" << 3);

Это не работает:

void log(const ostream& os)
{
  std::streambuf* buf = os.rdbuf();
  if( buf && typeid(*buf) == typeid(std::stringbuf) )
  {
    const std::string& format = dynamic_cast<std::stringbuf&>(*buf).str();
    cout << format << endl;
  }
}

приводит к «формату», содержащему ненужные данные вместо обычной правильной строки.

Я думаю, это потому, что временный ostream, возвращаемый оператором ‹‹, переживает строковый поток, из которого он исходит.

Или я ошибаюсь?

(Почему string() работает таким образом? Это потому, что он возвращает ссылку на себя? Я предполагаю, что да.)

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

Любые указатели или трюки, чтобы сделать это таким образом, будут приветствоваться. В моем реальном решении у меня есть много разных функций журнала, и все они сложнее, чем эта. Поэтому я бы предпочел, чтобы это как-то реализовано в вызывающем коде. (И не изменяя мой #define, если это возможно)

Просто чтобы дать представление, пример одного из моих реальных #define:

#define LOG_DEBUG_MSG(format, ...) \
  LogMessage(DEBUG_TYPE, const char* filepos, sizeof( __QUOTE__( @__VA_ARGS__ )), \
  format, __VA_ARGS__)

который соответствует varargs функциям журнала, подобным printf, принимающим char*, string() и ostream(), а также функциям без vararg, принимающим string(), exception() и HRESULT.


person Marius    schedule 08.10.2009    source источник
comment
Что значит не работает?   -  person Éric Malenfant    schedule 09.10.2009
comment
Вы правы, вы должны взять копию строки, поэтому format должен быть типа std::string, а не типа const std::string &. Однако вы можете просто поместить dynamic_cast в выражение cout и полностью потерять переменную.   -  person KayEss    schedule 09.10.2009
comment
Нет, мне не нужно делать копию. Содержимое строки, возвращаемой функцией str(), гарантированно останется постоянным (во многом так же, как это делает string::c_str()) между последующими вызовами этих методов. Что касается того, почему я делаю это так, мне нужна строка формата, поскольку я действительно хочу передать ее либо функции с одним параметром, принимающей строку, либо методу VARARGS, принимающему char * в зависимости от того, какие другие параметры получены. (Но это все выходит за рамки моего вопроса, на который был дан удовлетворительный ответ)   -  person Marius    schedule 10.10.2009
comment
Просто чтобы было понятно, мое решение на данный момент log(stringstream().flush() << "hello " << 1);, но спасибо за все остальные идеи.   -  person Marius    schedule 10.10.2009


Ответы (2)


Думаю, я понимаю, что происходит. Это дает ожидаемый результат:

log(std::stringstream() << 1 << "hello");

пока это не так:

log(std::stringstream() << "hello" << 1);

(он пишет шестнадцатеричное число, за которым следует цифра «1»)

Несколько элементов для объяснения:

  • Значение r не может быть привязано к неконстантной ссылке
  • Можно вызывать функции-члены во временном
  • std::ostream имеет оператор-член‹‹(void*)
  • std::ostream имеет оператор-член‹‹(int)
  • Для char* оператор не является членом, это operator‹‹(std::ostream&, const char*)

В приведенном выше коде std::stringstream() создает временное значение (rvalue). Его время жизни не проблематично, так как оно должно длиться все полное выражение, в котором оно объявлено (т. е. до тех пор, пока вызов log() не вернется).

В первом примере все работает нормально, потому что сначала вызывается член operator‹‹(int), а затем возвращаемая ссылка может быть передана оператору‹‹(ostream&, const char*)

Во втором примере оператор‹‹(не может быть вызван с "std::stringstream()" в качестве 1-го аргумента, так как это потребует его привязки к неконстантной ссылке. Однако член operator‹‹(void *) нормально, так как это член.

Кстати: почему бы не определить функцию log() как:

void log(const std::ostream& os)
{
    std::cout << os.rdbuf() << std::endl;
}
person Éric Malenfant    schedule 09.10.2009
comment
Спасибо за информацию! Это привело меня к предпочитаемому мной решению... Я знал, что нахожусь на правильном пути. Таким образом, лучший способ заставить его работать стабильно выглядит следующим образом: log(stringstream().flush() << "hello " << 1); - person Marius; 10.10.2009
comment
PS: Что означает БЮТ? И чем вы определяете функцию log() иначе, чем я? - person Marius; 10.10.2009
comment
Упс! Это опечатка. Я хотел написать By The Way. Исправлено - person Éric Malenfant; 12.10.2009
comment
Ааааа! Оглядываясь назад, было бы короче написать его полностью :) - person Éric Malenfant; 13.10.2009

Измените свой макрос LOG() на это:

#define LOG(x) do { std::stringstream s; s << x; log(s.str()); } while(0)

Это позволит вам использовать следующий синтаксис в ваших журналах отладки, поэтому вам не нужно вручную создавать поток строк.

LOG("Testing" << 1 << "two" << 3);

Затем определите его на ничего для выпуска, и у вас не будет лишних выделений.

person John Millikin    schedule 08.10.2009
comment
Вам не нужно делать/пока вокруг блока. У вас могут быть отдельно стоящие фигурные скобки только для управления областью выражений. - person KayEss; 09.10.2009
comment
Моя основная цель — оптимизация. как бы я ни хотел сохранить один макрос LOG(), который работает для многих разных переданных типов, мне, вероятно, потребуется создать исключение для этого, чтобы отличить его. проблема в том, что действительно все еще будут оставаться некоторые журналы в режиме выпуска (в зависимости от серьезности ведения журнала), и для них я бы не хотел описанных выше накладных расходов, поэтому мне может потребоваться создать исключение #define для каждой серьезности. Я пытался максимально сохранить формат синтаксиса ведения журнала в этом унаследованном проекте. - person Marius; 09.10.2009
comment
Я отмечаю это как полезное, но пока не буду отмечать его как решение. - person Marius; 09.10.2009
comment
@KayEss: do/while позволяет добавить точку с запятой в конец строки, что делает вызов LOG() более естественным. См. stackoverflow.com/questions/154136. - person John Millikin; 09.10.2009
comment
@Marius: Хотя я полностью понимаю цель использования макроса унифицированного ведения журнала, на практике его очень сложно реализовать должным образом. Использование отдельных макросов LOG_DEBUG(), LOG_WARNING(), LOG_ERROR() и т. д. гораздо удобнее. - person John Millikin; 09.10.2009
comment
@Millikin: Вы можете попробовать сделать что-нибудь, используя препроцессор boost. /**/#define LOG_WARN(...) printf( VA_ARGS)/*LINE BREAK*/ #define LOG(LV, ...)/*LINE BREAK*/BOOST_PP_CAT( LOG_, LV )( VA_ARGS )/*РАЗРЫВ СТРОКИ*/ ЖУРНАЛ(ПРЕДУПРЕЖДЕНИЕ,ТЕСТИРОВАНИЕ); /*cpp расширяет это до printf(TESTING); */ - person KitsuneYMG; 09.10.2009
comment
конечно, VA_ARGS должен иметь двойное подчеркивание в начале и в конце. - person KitsuneYMG; 09.10.2009
comment
@Millikin: Спасибо за объяснение do-while ... Квадратные скобки служат двойной цели: во-первых, они создают область действия для переменной «s», а также группируют операторы, когда они являются частью, скажем, оператора if/else без квадратных скобок. Но действительно ли это необходимо? Конечно, можно просто иметь { ... }; посреди кода без проблем? Кроме того, у меня есть отдельные вызовы LOG_DEBUG() LOG_INFO(), что я имел в виду под унифицированным, так это то, что каждый из этих серьезностей может обрабатывать несколько типов INFO, DEBUG и т. д. Типы списка параметров сообщений. - person Marius; 10.10.2009
comment
@Marius: см. ссылку, которую я разместил. Просто использование фигурных скобок может быть недопустимым синтаксисом, в зависимости от того, где используется макрос. Я не понимаю вторую часть вашего комментария - не могли бы вы уточнить это в вопросе? - person John Millikin; 10.10.2009
comment
@Millikin: я имею в виду только то, что согласен с вами в том, что отдельные определения LOG делают его более управляемым. Я могу полностью удалить ведение журнала определенной серьезности в разных сборках и т. Д. Что я и сделал. Под унификацией я подразумеваю, что один макрос LOG_XXXX будет расширяться и фактически соответствовать одному из нескольких перегруженных методов (а также varargs). Таким образом, под унификацией я подразумеваю, что одно определение, скажем, для DEBUG будет обслуживать несколько различных типов списков параметров. - person Marius; 10.10.2009