Moje pytanie można sprowadzić do tego, gdzie stringstream.str().c_str()
w pamięci znajduje się zwracany ciąg i dlaczego nie można go przypisać do const char*
?
Ten przykład kodu wyjaśni to lepiej niż ja
#include <string>
#include <sstream>
#include <iostream>
using namespace std;
int main()
{
stringstream ss("this is a string\n");
string str(ss.str());
const char* cstr1 = str.c_str();
const char* cstr2 = ss.str().c_str();
cout << cstr1 // Prints correctly
<< cstr2; // ERROR, prints out garbage
system("PAUSE");
return 0;
}
Założenie, które stringstream.str().c_str()
można przypisać do, const char*
doprowadziło do błędu, którego wytropienie zajęło mi trochę czasu.
Jeśli chodzi o punkty bonusowe, czy ktoś może wyjaśnić, dlaczego zastąpić cout
oświadczenie
cout << cstr // Prints correctly
<< ss.str().c_str() // Prints correctly
<< cstr2; // Prints correctly (???)
drukuje napisy poprawnie?
Kompiluję w Visual Studio 2008.
źródło
str()
jest zaimplementowane w taki sposób, że RVO może się uruchomić (co jest bardzo prawdopodobne), kompilator może bezpośrednio skonstruować wynik dotmp
, eliminując tymczasowe; a każdy nowoczesny kompilator C ++ zrobi to, gdy optymalizacje są włączone. Oczywiście rozwiązanie bind-to-const-reference gwarantuje brak kopiowania, więc może być lepsze - ale pomyślałem, że nadal warto to wyjaśnić.To, co robisz, to tworzenie tymczasowego. Ten tymczasowy istnieje w zakresie określonym przez kompilator, tak że jest wystarczająco długi, aby spełnić wymagania miejsca, w którym się znajduje.
Gdy tylko instrukcja
const char* cstr2 = ss.str().c_str();
jest kompletna, kompilator nie widzi powodu, aby trzymać tymczasowy ciąg w pobliżu i jest on niszczony, a zatemconst char *
wskazuje na zwolnioną pamięć.Twoja instrukcja
string str(ss.str());
oznacza, żestring
zmienna tymczasowa jest używana w konstruktorze dla zmiennejstr
, którą umieściłeś na lokalnym stosie i pozostaje tak długo, jak można się spodziewać: do końca bloku lub funkcji, którą napisałeś. Dlategoconst char *
wnętrze jest nadal dobrą pamięcią, gdy spróbujeszcout
.źródło
W tej linii:
ss.str()
utworzy kopię zawartości łańcucha ciągów. Kiedy wywołujeszc_str()
tę samą linię, będziesz odnosić się do prawdziwych danych, ale po tej linii ciąg zostanie zniszczony, pozostawiającchar*
wskazanie pamięci , której nie jesteś właścicielem.źródło
Obiekt std :: string zwrócony przez ss.str () jest obiektem tymczasowym, którego czas życia będzie ograniczony do wyrażenia. Nie możesz więc przypisać wskaźnika do tymczasowego obiektu bez wyrzucania śmieci.
Teraz jest jeden wyjątek: jeśli używasz odwołania do const, aby uzyskać obiekt tymczasowy, legalne jest używanie go przez dłuższy czas. Na przykład powinieneś zrobić:
W ten sposób otrzymujesz sznurek na dłuższy czas.
Teraz musisz wiedzieć, że istnieje rodzaj optymalizacji zwany RVO, który mówi, że jeśli kompilator zobaczy inicjalizację za pośrednictwem wywołania funkcji, a ta funkcja zwróci wartość tymczasową, nie wykona kopii, ale po prostu sprawi, że przypisana wartość będzie tymczasowa . W ten sposób nie musisz faktycznie używać referencji, tylko wtedy, gdy chcesz mieć pewność, że nie skopiuje się, że jest to konieczne. Więc robiąc:
byłoby lepsze i prostsze.
źródło
Plik
ss.str()
tymczasowy jest niszczony po zakończeniu inicjalizacjicstr2
. Więc kiedy drukujesz to za pomocącout
, ciąg c, który był powiązany z tymstd::string
tymczasem, już dawno został zniszczony, a zatem będziesz miał szczęście, jeśli ulegnie awarii i potwierdzi, a nie będzie szczęścia, jeśli drukuje śmieci lub wydaje się działać.C-string, w którym
cstr1
wskazuje, jest jednak powiązany z ciągiem, który nadal istnieje w momencie wykonywaniacout
- więc poprawnie drukuje wynik.W poniższym kodzie pierwszy
cstr
jest poprawny (zakładam, że jestcstr1
w prawdziwym kodzie?). Drugi drukuje łańcuch c powiązany z tymczasowym obiektem ciąguss.str()
. Obiekt jest niszczony pod koniec oceny pełnego wyrażenia, w którym się pojawia. Pełne wyrażenie jest całymcout << ...
wyrażeniem - więc gdy wyprowadzany jest łańcuch c, powiązany obiekt ciągu nadal istnieje. Bocstr2
- to czyste zło, że się udaje. Najprawdopodobniej wewnętrznie wybiera tę samą lokalizację przechowywania dla nowego tymczasowego, który już wybrał dla tymczasowego używanego do inicjalizacjicstr2
. Może również ulec awarii.Zwrot
c_str()
will zwykle po prostu wskazuje na wewnętrzny bufor ciągów - ale nie jest to wymagane. Łańcuch mógłby stanowić bufor, jeśli na przykład jego wewnętrzna implementacja nie jest ciągła (jest to całkiem możliwe - ale w następnym standardzie C ++ ciągi muszą być przechowywane w sposób ciągły).W GCC ciągi używają liczenia odwołań i kopiowania przy zapisie. W związku z tym przekonasz się, że spełnione są następujące warunki (tak jest, przynajmniej w mojej wersji GCC)
Te dwa ciągi mają tutaj ten sam bufor. W momencie zmiany jednego z nich bufor zostanie skopiowany, a każdy będzie przechowywać oddzielną kopię. Jednak inne implementacje stringów działają inaczej.
źródło