2009-09-03 10 views
120

Mi pregunta se puede resumir a, ¿dónde está la cadena devuelta desde stringstream.str().c_str() en vivo en la memoria y por qué no puede asignarse a const char*?stringstream, string y char * confusión de conversión

Este ejemplo de código se lo explicará mejor que yo

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

La suposición de que stringstream.str().c_str() podrían ser asignados a un const char* dado lugar a un error que me tomó un tiempo para rastrear.

de puntos de bonificación, ¿alguien puede explicar por qué la sustitución de la declaración cout con

cout << cstr   // Prints correctly 
    << ss.str().c_str() // Prints correctly 
    << cstr2;   // Prints correctly (???) 

muestra las cadenas correctamente?

Estoy compilando en Visual Studio 2008.

Respuesta

172

stringstream.str() devuelve un objeto de cadena temporal que se destruye al final de la expresión completa. Si obtiene un puntero a una cadena C de eso (stringstream.str().c_str()), apuntará a una cadena que se elimina donde termina la instrucción. Es por eso que tu código imprime basura.

Se puede copiar ese objeto cadena temporal a algún otro objeto de cadena y tomar la cadena C de que uno:

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

Nota de haber tomado la cadena temporal const, ya que cualquier cambio en él podrían hacer que se reasignar y así hacer cstr inválido. Es por lo tanto más seguro para no almacenar el resultado de la llamada a str() en absoluto y utilizar cstr sólo hasta el final de la expresión total:

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

Por supuesto, esta última no puede ser fácil y copia podría ser demasiado costoso. Lo que puede hacer en su lugar es vincular el temporal a una referencia const. Esto extenderá su vida útil a la duración de la referencia:

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

IMO esa es la mejor solución. Desafortunadamente no es muy conocido.

+12

Se debe tener en cuenta que hacer una copia (como en el primer ejemplo) no necesariamente implicará una sobrecarga, si 'str()' se implementa de tal forma que RVO pueda activarse (lo cual es muy probable), el compilador puede construir el resultado directamente en 'tmp', elidiendo el temporal; y cualquier compilador moderno de C++ lo hará cuando las optimizaciones estén habilitadas. Por supuesto, la solución bind-to-const-reference garantiza la ausencia de copia, por lo que puede ser preferible, pero pensé que todavía valía la pena aclararla. –

+1

"Por supuesto, la solución bind-to-const-reference garantiza que no se copia" <- no es así. En C++ 03, el constructor de copia debe ser accesible y la implementación puede copiar el inicializador y vincular la referencia a la copia. –

+1

Su primer ejemplo es incorrecto. El valor devuelto por c_str() es transitorio. No se puede confiar después del final de la declaración actual. Por lo tanto, puede usarlo para pasar un valor a una función, pero NUNCA debe asignar el resultado de c_str() a una variable local. –

12

Lo que estás haciendo es crear un temporal. Ese temporal existe en un ámbito determinado por el compilador, de modo que sea lo suficientemente largo como para satisfacer los requisitos de hacia dónde se dirige.

Tan pronto como se completa la instrucción const char* cstr2 = ss.str().c_str();, el compilador no ve razón para mantener la cadena temporal, y se destruye, y así su const char * apunta a la memoria libre.

Su estado de string str(ss.str()); significa que el temporal se utiliza en el constructor para la variable stringstr que has puesto en la pila local y que se mantiene tanto tiempo como era de esperar: hasta el final del bloque, o función que has escrito Por lo tanto, el const char * dentro sigue siendo buena memoria cuando prueba el cout.

5

En esta línea:

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

ss.str() hará una copia de los contenidos de la stringstream. Cuando llame al c_str() en la misma línea, hará referencia a datos legítimos, pero después de esa línea se destruirá la cadena, dejando su char* para apuntar a la memoria sin propietario.

4

El objeto std :: string devuelto por ss.str() es un objeto temporal que tendrá un tiempo de vida limitado a la expresión. Por lo tanto, no puede asignar un puntero a un objeto temporal sin obtener basura.

Ahora, hay una excepción: si utiliza una referencia constante para obtener el objeto temporal, es legal usarlo por un tiempo de vida más largo. Por ejemplo, debe hacer:

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

De esta forma obtendrá la cadena por un tiempo más prolongado.

Ahora, debes saber que hay un tipo de optimización llamada RVO que dice que si el compilador ve una inicialización a través de una llamada a función y devuelve una función temporal, no hará la copia sino que simplemente asignará la el valor sea el temporal. De esta forma, no necesita usar una referencia, solo si quiere asegurarse de que no se copie que sea necesaria. Haciendo así:

std::string resultstr = ss.str(); 
const char* cstr2 = resultstr.c_str(); 

sería mejor y más simple.

5

El ss.str() temporal se destruye después de que se completa la inicialización de cstr2. Entonces, cuando lo imprime con cout, la cadena que se asoció con ese std::string temporal ha sido descartada por mucho tiempo, y por lo tanto tendrá suerte si falla y afirma, y ​​no tiene suerte si imprime basura o si parece funcionar.

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

La C-secuencia, donde cstr1 puntos que, sin embargo, se asocia con una cadena que todavía existe en el momento que haces la cout - por lo que se imprime correctamente el resultado.

En el siguiente código, el primer cstr es correcto (supongo que es cstr1 en el código real?). El segundo imprime la cadena c asociada con el objeto de cadena temporal ss.str(). El objeto se destruye al final de la evaluación de la expresión completa en la que aparece. La expresión completa es la expresión completa cout << ..., de modo que mientras se emite la cadena c, el objeto cadena asociado todavía existe. Para cstr2 - es pura maldad que tiene éxito. Es muy posible que internamente elija la misma ubicación de almacenamiento para el nuevo temporal que ya eligió para el temporal utilizado para inicializar cstr2. Podría también colapsar.

cout << cstr   // Prints correctly 
    << ss.str().c_str() // Prints correctly 
    << cstr2;   // Prints correctly (???) 

El regreso de c_str() por lo general acaba de señalar el búfer de cadena interna - pero eso no es un requisito. La cadena podría formar un búfer si su implementación interna no es contigua, por ejemplo (eso es muy posible, pero en el próximo estándar de C++, las cadenas deben almacenarse contiguamente).

En GCC, las cadenas utilizan el recuento de referencias y el copy-on-write.Por lo tanto, se encuentra que la siguiente es cierto (lo hace, al menos en mi versión de GCC)

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

Las dos cadenas comparten el mismo tampón aquí. En el momento en que cambie uno de ellos, se copiará el búfer y cada uno tendrá su copia por separado. Sin embargo, otras implementaciones de cadenas hacen cosas diferentes.

Cuestiones relacionadas