Мой вопрос можно свести к следующему: где строка, возвращенная из stringstream.str().c_str()
памяти, находится в памяти и почему ее нельзя присвоить a const char*
?
Этот пример кода объяснит это лучше, чем я могу
#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;
}
Предположение, которое stringstream.str().c_str()
можно отнести к какой- const char*
либо ошибке, привело к ошибке, на отслеживание которой мне потребовалось время.
Что касается бонусных баллов, может ли кто-нибудь объяснить, почему замена cout
заявления на
cout << cstr // Prints correctly
<< ss.str().c_str() // Prints correctly
<< cstr2; // Prints correctly (???)
правильно печатает строки?
Я компилирую в Visual Studio 2008.
источник
str()
он реализован таким образом, что RVO может сработать (что очень вероятно), компилятору разрешено создавать результат напрямую вtmp
, исключая временное; и любой современный компилятор C ++ сделает это, если включена оптимизация. Конечно, решение bind-to-const-reference гарантирует отсутствие копирования, поэтому может быть предпочтительнее, но я подумал, что это все же стоит уточнить.Вы создаете временный. Этот временный объект существует в области, определяемой компилятором, так что его достаточно долго, чтобы удовлетворить требованиям того, куда он идет.
Как только оператор
const char* cstr2 = ss.str().c_str();
завершен, компилятор не видит причин для сохранения временной строки, и она уничтожается, и, таким образом, выconst char *
указываете на освободившуюся память.Ваш оператор
string str(ss.str());
означает, что временное значение используется в конструкторе дляstring
переменной,str
которую вы поместили в локальный стек, и остается там столько, сколько вы ожидаете: до конца блока или функции, которую вы написали. Поэтому,const char *
когда вы пробуете файлcout
.источник
В этой строке:
const char* cstr2 = ss.str().c_str();
ss.str()
сделает копию содержимого строкового потока. Когда вы вызываетеc_str()
в той же строке, вы будете ссылаться на допустимые данные, но после этой строки строка будет уничтожена, и вы укажетеchar*
на незарегистрированную память.источник
Объект std :: string, возвращаемый ss.str (), является временным объектом, время жизни которого будет ограничено выражением. Таким образом, вы не можете назначить указатель на временный объект, не получив корзины.
Теперь есть одно исключение: если вы используете ссылку на константу для получения временного объекта, законно использовать ее в течение более длительного времени. Например, вам следует сделать:
#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 std::string& resultstr = ss.str(); const char* cstr2 = resultstr.c_str(); cout << cstr1 // Prints correctly << cstr2; // No more error : cstr2 points to resultstr memory that is still alive as we used the const reference to keep it for a time. system("PAUSE"); return 0; }
Так вы получите веревку на более долгое время.
Теперь вы должны знать, что существует своего рода оптимизация, называемая RVO, которая гласит, что если компилятор видит инициализацию через вызов функции и эта функция возвращает временное значение, он не будет выполнять копию, а просто сделает назначенное значение временным. . Таким образом, вам не нужно фактически использовать ссылку, только если вы хотите быть уверены, что она не скопирует то, что необходимо. Так поступаем:
std::string resultstr = ss.str(); const char* cstr2 = resultstr.c_str();
было бы лучше и проще.
источник
ss.str()
Временный разрушается после инициализацииcstr2
завершена. Поэтому, когда вы печатаете его с помощьюcout
, c-строка, которая была связана с этимstd::string
временным, уже давно уничтожена, и поэтому вам повезет, если он выйдет из строя и подтвердится, и не повезет, если он распечатает мусор или, похоже, работает.const char* cstr2 = ss.str().c_str();
Однако C-строка, на которую
cstr1
указывает, связана со строкой, которая все еще существует в момент, когда вы делаете это,cout
поэтому она правильно печатает результат.В следующем коде первый
cstr
правильный (я полагаю, этоcstr1
настоящий код?). Второй выводит c-строку, связанную с временным строковым объектомss.str()
. Объект уничтожается в конце оценки полного выражения, в котором он появляется. Полное выражение - это всеcout << ...
выражение, поэтому, пока выводится c-строка, связанный строковый объект все еще существует. Ибоcstr2
то, что он добивается успеха - это чистое зло. Скорее всего, он внутренне выбирает то же место хранения для нового временного файла, который он уже выбрал для временного, используемого для инициализацииcstr2
. Он мог также разбиться.cout << cstr // Prints correctly << ss.str().c_str() // Prints correctly << cstr2; // Prints correctly (???)
Возврат
c_str()
обычно просто указывает на внутренний строковый буфер, но это не является обязательным требованием. Строка может составлять буфер, например, если ее внутренняя реализация не является смежной (это вполне возможно, но в следующем стандарте C ++ строки должны храниться непрерывно).В GCC строки используют подсчет ссылок и копирование при записи. Таким образом, вы обнаружите, что верно следующее (это так, по крайней мере, в моей версии GCC)
string a = "hello"; string b(a); assert(a.c_str() == b.c_str());
Здесь две строки используют один и тот же буфер. При изменении одного из них буфер будет скопирован, и каждый будет содержать свою отдельную копию. Однако другие строковые реализации работают иначе.
источник