2009-10-08 10 views
6

Tworzę rejestrator z następujących sekcjach:stringstream problemem powrót tymczasowy ostream

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

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

na pomysł, aby zrobić:

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

To wszystko działa zgodnie z przeznaczeniem, ale kiedy zrobić:

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

To nie działa:

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; 
    } 
} 

powoduje wyświetlenie "formatu" zawierającego dane śmieci zamiast zwykłego prawidłowego ciągu.

Myślę, że dzieje się tak dlatego, że tymczasowy ostream zwrócony przez operatora < < przeżył ciąg strumienia, z którego pochodzi.

Czy ja się mylę?

(Dlaczego string() działa w ten sposób? Czy dlatego, że zwraca odwołanie do siebie? Zakładam, że tak.)

naprawdę chciałbym zrobić to w ten sposób, jak chciałbym być wyeliminowanie dodatkowy przydział przy logowaniu w trybie zwolnienia.

Wszelkie wskazówki i triki, aby to zrobić w ten sposób, byłyby mile widziane. W moim rzeczywistym rozwiązaniu mam wiele różnych funkcji dziennika i wszystkie są bardziej złożone niż to. Więc wolałbym, aby to zostało jakoś zaimplementowane w kodzie wywołującym. (A nie przez modyfikację mojego #define jeśli to możliwe)

Wystarczy dać wyobrażenie, przykład jednego z moich rzeczywistych #defines:

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

który pasuje varargs printf-podobne funkcje dziennika biorąc char *, Funkcje string() i ostream() oraz funkcje inne niż vararg przy użyciu string(), exception() i HRESULT.

+0

Co masz na myśli, mówiąc "to nie działa"? –

+0

Masz rację, powinieneś wziąć kopię ciągu, więc 'format' powinien być typu' std :: string', a nie typu 'const std :: string &'. Możesz jednak po prostu wstawić 'dynamic_cast' do wyrażenia' cout' i całkowicie stracić zmienną. – KayEss

+0

Nie, nie muszę robić kopii. Zawartość ciągu zwracanego przez str() ma gwarantowaną stałą wartość (podobnie jak string :: c_str()) pomiędzy kolejnymi wywołaniami tych metod. Jeśli chodzi o to, dlaczego robię to w ten sposób, potrzebuję ciąg formatu, ponieważ faktycznie chcę go przekazać albo do funkcji pojedynczego parametru, biorąc ciąg znaków, albo do metody VARARGS przyjmującej znak * w zależności od tego, jakie inne parametry są odbierane. (Ale to wszystko wykracza poza zakres mojego pytania - na co odpowiedziano zadowalająco). – Marius

Odpowiedz

7

I think Widzę, co się dzieje.To daje oczekiwany wynik:

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

Choć nie:

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

(zapisuje numer szesnastkowy, a następnie na "1" cyfra)

kilka elementów dla wyjaśnienia :

  • rvalue nie może być związana z const odniesieniu
  • Jest OK, aby wywołać funkcje składowe czasowo
  • std :: ostream ma operator członkiem < < (void *)
  • std :: ostream ma operatora Państwa < < (int)
  • Dla char * operator nie jest członkiem, to operator < < (std :: ostream &, const char *)

W powyższym kodzie, std :: stringstream() tworzy tymczasowy (rvalue). Jego żywotność nie jest problematyczna, ponieważ musi trwać dla całego pełnego wyrażenia, do którego została zadeklarowana (tj. Do momentu, gdy wywołanie funkcji log() powróci).

W pierwszym przykładzie, wszystko działa ok, ponieważ operator członkiem < < (int) najpierw sprawdził, a następnie odniesienie zwrócone mogą być przekazywane do operatora < < (ostream &, const char *)

W drugi przykład, operator < < (nie może być wywołany z "std :: stringstream()" jako pierwszy argument, ponieważ wymagałoby to powiązania z niestanowiącą odniesienia referencją, jednak operator członkowski < < (void *) jest ok, ponieważ jest członkiem.

Przy okazji: dlaczego nie zdefiniować funkcji log() jako:

void log(const std::ostream& os) 
{ 
    std::cout << os.rdbuf() << std::endl; 
} 
+1

Dzięki za informacje! To doprowadziło mnie do mojego preferowanego rozwiązania ... Wiedziałem, że jestem na dobrej drodze. Najlepszym sposobem dla mnie, aby to działało konsekwentnie jest następująca: 'log (stringstream(). Flush() <<" hello "<< 1);' – Marius

+0

PS: Co to jest BYT? A jak definiujesz funkcję 'log()' inaczej niż ja? – Marius

+0

Oups! To literówka. Chciałem napisać "By the Way". Poprawiony –

6

zmienić swój LOG() makro do tego:

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

który pozwoli Ci użyć następującej składni w dziennikach debugowania, dzięki czemu nie trzeba ręcznie zbudować ciąg strumienia.

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

Zdefiniuj go, aby uzyskać zwolnienie, a nie będziesz mieć żadnych dodatkowych przydziałów.

+0

Nie potrzebujesz do/dookoła bloku. Możesz mieć swobodne aparaty stojące tylko po to, by kontrolować zakres wyrażeń. – KayEss

+0

Moim głównym celem jest optymalizacja. tak bardzo jak chciałbym zachować pojedyncze makro LOG(), które działa dla wielu różnych typów, muszę prawdopodobnie stworzyć wyjątek dla tego, aby go rozróżnić. Problem polega na tym, że w dalszym ciągu niektóre dzienniki pozostaną w trybie zwolnienia (w zależności od ważności rejestrowania), a dla nich nie chciałbym, aby koszty zostały opisane powyżej, więc może być konieczne utworzenie wyjątku #define dla każdego poziomu ważności. Próbowałem zachować format składni logowania niezmieniony tak bardzo jak to możliwe w tym starszym projekcie. – Marius

+0

Oznaczam to jako przydatne, ale nie oznaczę go jeszcze jako rozwiązanie. – Marius