2011-12-05 25 views
13

En un proyecto mío en C++ estoy un paso antes de reemplazar todo char* con std::string pero encuentro una cierta ocasión en la que std::string falla miserablemente.Cadena C++ no optimizada lo suficiente para literales de cadena

Imagínese que tengo estas 2 funciones:

void foo1(const std::string& s) 
{ 
    ... 
} 

void foo2(const char* s) 
{ 
    ... 
} 

Si escribo algo como esto:

const char* SL = "Hello to all!"; 

foo1(SL); // calls malloc, memcpy, free 
foo2(SL); 

en foo1 la SL implícitamente convertido en std::string. Esto significa que el constructor std::string asignará memoria y se copiará la cadena literal a ese búfer. En foo2 aunque nada de todo esto sucederá.

En la mayoría de las implementaciones std::string se supone que es súper optimizado (copiar al escribir, por ejemplo), pero cuando se construyo con un const char* no lo es. Y mi pregunta es esta: ¿Por qué sucede esto? ¿Me estoy perdiendo de algo? ¿Mi biblioteca estándar no está optimizada lo suficiente o por alguna razón (de la que no tengo conocimiento) esto es totalmente inseguro?

+4

Copiar en escritura no es realmente una "súper optimización". Creo que la biblioteca estándar de GCC todavía lo usa, pero solo porque tenía sentido hace 10 años, antes de que la norma fuera el multihilo. Una implementación sensata de la biblioteca hecha hoy evitaría la VACA como la peste. – jalf

+2

Creo que el estándar ni siquiera permite COW debido a los requisitos de invalidación del iterador de las funciones miembro. – Xeo

+4

Afaik C++ 03 permitido VACA. Creo que C++ 11 lo prohíbe – jalf

Respuesta

10

El problema es que no hay manera para que la clase std :: string para reconocer si el puntero const char* es un carácter global literal o no:

const char *a = "Hello World"; 
const char *b = new char[20]; 

El char * puntero no válido podría obtener en cualquier momento (por ejemplo, cuando se trata de una variable local y termina la función/alcance), por lo tanto std::string debe convertirse en un propietario exclusivo de la cadena. Esto solo se puede lograr copiando.

El siguiente ejemplo demuestra por qué es necesario:

std::string getHelloWorld() { 
    char *hello = new char[64]; 
    strcpy(hello, "Hello World"); 
    std::string result = (const char *)hello; // If std::string didn't make a copy, the result could be a garbage 
    delete[] hello; 
    return result; 
} 
+0

En realidad, una cadena literal es un 'char [N]' donde N es la longitud + 1 (terminador nulo). – Xeo

+2

¿Por qué el 'nuevo'? La asignación del búfer en la pila también funcionaría: 'char const hello [] =" Hello World ";' –

+1

@MatthieuM .: Y su versión sería excepcionalmente segura, mientras que dark_charlie no es como la cadena-constructor no es no- lanzar. –

5

std::string no es una bala de plata. Está destinado a ser la mejor implementación posible de una cadena mutable de propósito general que posee su memoria, y que es razonablemente barata de usar con las API de C. Esos son escenarios comunes, pero que no se corresponden con cada ejemplo del uso de cadenas. literales

de Cuerda, como usted menciona, no encajan bien este caso de uso. Utilizan memoria asignada estáticamente, por lo que std::string no puede ni debe tratar de tomar posesión de la memoria. Y estas cadenas son siempre , por lo que no se puede std::string permitirá modificar ellas de sólo lectura.

std::string crea una copia de los datos de cadena transferidos a la misma y, a continuación, trabaja en esta copia internamente.

Si desea operar cadenas constantes cuya vida útil se maneje en otro lugar (en el caso de cadenas literales, es manejada por la biblioteca de tiempo de ejecución que inicializa y libera datos estáticos), puede usar una representación de cadena diferente. Tal vez solo un simple const char*.

+0

* Es la mejor implementación posible de una cadena mutable que posee su memoria. * No. Ni siquiera cerca. El requisito estricto impuesto por los costos '.c_str()'. Una mejor implementación posible probablemente usaría B-Trees para evitar todas esas costosas reasignaciones cuando la cadena es grande y se modifica. –

+0

pero luego perdería contigüidad, lo que haría que la conversión a cadenas C sea más costosa. Hay muchas concesiones para ser consideradas. :) Pero aclaré mi respuesta un poco. – jalf

+0

Sí, esta es la razón por la que mencioné 'c_str'. Creo que el SGI STL tenía una clase 'cuerda' para cubrir el caso cuando no se requiere interacción C. –

21

En realidad, sus preocupaciones se iría (*) si ha cambiado el literal:

std::string const SL = "Hello to all!"; 

que añade el const para usted.

Ahora, llamando foo1 no implicará ninguna copia (en absoluto), y llamando foo2 se puede lograr a bajo costo:

foo1(SL);   // by const-reference, exact same cost than a pointer 
foo2(SL.c_str()); // simple pointer 

Si desea mover a std::string, no sólo intercambiar las funciones interfaces, cambie las variables (y constantes) también.

(*) La respuesta original supone que SL fue una constante global, si se trata de una variable local a una función, entonces se podría hacer static si uno desea realmente para evitar la construcción de ésta en cada llamada.

+0

Un punto adicional: si el literal de cadena está en una función, es posible que desee hacerlo estático. –

+4

¿Eso no significa que todos sus literales de cadena se copiarán al montón al inicio? –

+0

¿Por qué las preocupaciones desaparecerían? Ahora, el objeto std :: string se construirá/destruirá en el alcance de entrada/salida, lo que podría conducir a la misma memoria alloc/dealloc que anteriormente (dependiendo de la implementación de std :: string). Hacerlo const no lo hace estático, ¿verdad? –

Cuestiones relacionadas