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, ако е възможно)

Само за да дам идея, пример за една от действителните ми #defines:

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

което съответства на varargs printf-подобни log функции, приемащи 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")

Няколко елемента за обяснение:

  • Rvalue не може да бъде обвързано с неконстантна препратка
  • Добре е да извиквате членски функции на временен
  • std::ostream има член оператор‹‹(void*)
  • std::ostream има член оператор‹‹(int)
  • За char* операторът не е член, той е operator‹‹(std::ostream&, const char*)

В кода по-горе std::stringstream() създава временна (rvalue). Неговият живот не е проблематичен, тъй като трябва да продължи за целия пълен израз, в който е деклариран (т.е. докато извикването на log() се върне).

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

Във втория пример operator‹‹(не може да бъде извикан с „std::stringstream()“ като първи аргумент, тъй като това би изисквало той да бъде обвързан с неконстантна препратка. Операторът-член обаче‹‹(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: Какво означава BYT? И как дефинирате функцията log() различно от мен? - person Marius; 10.10.2009
comment
Опа! Това е печатна грешка. Исках да напиша Между другото. Поправено - 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
Не се нуждаете от do/while около блока. Можете да имате свободно стоящи скоби, само за да контролирате обхвата на изразите. - 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: Можете да опитате да направите нещо с помощта на усилващ препроцесор. /**/#define LOG_WARN(...) printf( VA_ARGS )/*LINE BREAK*/ #define LOG(LV, ...)/*LINE BREAK*/BOOST_PP_CAT( LOG_ , LV )( VA_ARGS )/*ПРЕКЪСВАНЕ НА РЕД*/ LOG(ПРЕДУПРЕЖДЕНИЕ,ТЕСТВАНЕ); /*cpp разширява това до printf(ТЕСТВАНЕ); */ - 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 го правят по-управляем. Мога да премахна напълно регистрирането на определена сериозност в различни компилации и т.н. Което направих. Под unify имам предвид, че един макрос LOG_XXXX ще се разшири и всъщност ще съвпадне с един от няколко претоварени метода (както и един varargs). Така че под unify имам предвид, че дефинираният за, да речем, DEBUG ще обслужва няколко различни типа списъци с параметри. - person Marius; 10.10.2009