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

142

Мой вопрос можно свести к следующему: где строка, возвращенная из 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.

Графика Нуб
источник

Ответы:

203

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

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

SBI
источник
14
Следует отметить, что выполнение копирования (как в вашем первом примере) не обязательно приведет к каким-либо накладным расходам - ​​если str()он реализован таким образом, что RVO может сработать (что очень вероятно), компилятору разрешено создавать результат напрямую в tmp, исключая временное; и любой современный компилятор C ++ сделает это, если включена оптимизация. Конечно, решение bind-to-const-reference гарантирует отсутствие копирования, поэтому может быть предпочтительнее, но я подумал, что это все же стоит уточнить.
Павел Минаев
1
«Конечно, решение bind-to-const-reference гарантирует отсутствие копирования» <- это не так. В C ++ 03 конструктор копирования должен быть доступен, а реализации разрешено копировать инициализатор и связывать ссылку с копией.
Йоханнес Шауб -
1
Ваш первый пример неверен. Значение, возвращаемое c_str (), является временным. На него нельзя полагаться после окончания текущего заявления. Таким образом, вы можете использовать его для передачи значения функции, но НИКОГДА не должны назначать результат c_str () локальной переменной.
Мартин Йорк
2
@litb: Вы технически правы. Указатель действителен до следующего вызова бесплатного метода для строки. Проблема в том, что использование опасно по своей сути. Может быть, не для первоначального разработчика (хотя в данном случае так было), но особенно для последующих исправлений обслуживания, такой код становится чрезвычайно хрупким. Если вы хотите сделать это, вы должны обернуть область указателей так, чтобы ее использование было как можно короче (лучше всего это длина выражения).
Мартин Йорк,
1
@sbi: Хорошо, спасибо, это более понятно. Однако, строго говоря, поскольку переменная 'string str' не изменена в приведенном выше коде, str.c_str () остается вполне допустимым, но я понимаю потенциальную опасность в других случаях.
Уильям Найт,
13

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

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

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

Джаред Оберхаус
источник
6

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

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

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

Fbrereto
источник
5

Объект 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();

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

Клаим
источник
5

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

Здесь две строки используют один и тот же буфер. При изменении одного из них буфер будет скопирован, и каждый будет содержать свою отдельную копию. Однако другие строковые реализации работают иначе.

Йоханнес Шауб - litb
источник