Stringstream, string и char * путаница преобразования - программирование

Stringstream, string и char * путаница преобразования

Мой вопрос может быть сведен к тому, где строка, возвращаемая из stringstream.str().c_str() вживую в памяти, и почему она не может быть привязана к 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.

4b9b3361

Ответ 1

stringstream.str() возвращает временный объект строки, который был уничтожен в конце полного выражения. Если вы получите указатель на строку C из этого (stringstream.str().c_str()), он укажет на строку, которая будет удалена там, где заканчивается оператор. Вот почему ваш код печатает мусор.

Вы можете скопировать этот временный строковый объект на какой-то другой строковый объект и взять строку C из этого:

const std::string tmp = stringstream.str();
const char* cstr = tmp.c_str();

Обратите внимание, что я сделал временную строку const, потому что любые изменения в ней могут привести к ее перераспределению и, таким образом, сделать cstr недействительным. Для этого безопаснее не сохранять результат вызова str() вообще и использовать cstr только до конца полного выражения:

use_c_str( stringstream.str().c_str() );

Конечно, последнее может быть нелегким, и копирование может быть слишком дорогостоящим. Вместо этого вы можете привязать временную ссылку const. Это продлит срок его службы до времени жизни ссылки:

{
  const std::string& tmp = stringstream.str();   
  const char* cstr = tmp.c_str();
}

IMO - лучшее решение. К сожалению, это не очень хорошо известно.

Ответ 2

Что вы делаете, так это создание временного. Это временное существование существует в области, определяемой компилятором, так что она достаточно долго, чтобы удовлетворять требованиям того, где она движется.

Как только оператор const char* cstr2 = ss.str().c_str(); завершен, компилятор не видит причин поддерживать временную строку вокруг, и она уничтожается, и, таким образом, ваш const char * указывает на свободную память.

Ваше утверждение string str(ss.str()); означает, что временное значение используется в конструкторе для переменной string str, которую вы поместили в локальный стек, и это остается вокруг столько, сколько вы ожидали: до конец блока или функцию, которую вы написали. Поэтому const char * внутри остается хорошей памятью при попытке cout.

Ответ 3

В этой строке:

const char* cstr2 = ss.str().c_str();

ss.str() сделает копию содержимого строкового потока. Когда вы вызываете c_str() в той же строке, вы будете ссылаться на законные данные, но после этой строки строка будет уничтожена, оставив ваш char* указывать на незанятую память.

Ответ 4

Временной файл 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() обычно будет просто указывать на внутренний буфер строки, но это не требование. Строка может составлять буфер, если его внутренняя реализация не является смежной, например (что вполне возможно - но в следующем стандарте С++ строки должны быть смежно сохранены).

В GCC строки используют подсчет ссылок и копирование на запись. Таким образом, вы обнаружите, что выполняется следующее (оно, по крайней мере, в моей версии GCC)

string a = "hello";
string b(a);
assert(a.c_str() == b.c_str());

Две строки имеют один и тот же буфер. В то время, когда вы меняете один из них, буфер будет скопирован, и каждый из них сохранит свою отдельную копию. Тем не менее, другие реализаций строк делают разные вещи.

Ответ 5

Объект std::string, возвращаемый ss.str(), является временным объектом, который будет иметь время жизни, ограниченное выражением. Таким образом, вы не можете назначить указатель на временный объект, не получая мусор.

Теперь есть одно исключение: если вы используете ссылку на const для получения временного объекта, законно использовать его для более широкого срока службы. Например, вы должны:

#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();

будет лучше и проще.