2008-12-04 13 views
7

Estoy acostumbrado a pasar alrededor de cadena como esta en mis aplicaciones C++:C++ cadena que puede ser NULL

void foo(const std::string& input) 
{ 
    std::cout << input.size() << std::endl; 
} 

void bar() 
{ 
    foo("stackoverflow"); 
} 

Ahora tengo un caso en el que quiero que la cadena sea NULL:

void baz() 
{ 
    foo("stackoverflow"); 
    foo(NULL); // very bad with foo implementation above 
} 

pudiera cambiar foo a:

void foo(const std::string* input) 
{ 
    // TODO: support NULL input 
    std::cout << input->size() << std::endl; 
} 

Pero para pasar una cadena literal o copiar un char* a que la aplicación de foo tengo que escribir algo como esto:

void bar() 
{ 
    string input("hi"); // annoying temporary 
    foo(&input); 
    foo(NULL); // will work as long as foo handles NULL properly 
} 

empecé a pensar en la herencia de std::string y añadiendo una propiedad null, pero no estoy seguro de que sea una buena idea. Tal vez sea mejor simplemente usar una cadena const char* para los parámetros que pueden ser NULL, pero ¿qué ocurre si quiero guardar una copia de la cadena (o NULL) sin tener que administrar su memoria por mí mismo? (Consulte What are some of the drawbacks to using C-style strings? etc.)

¿Alguna solución inteligente?

+0

¿Por qué quiere un NULL? ¿Es suficiente? – Tim

+0

@Tim: Tal vez. Me ahorraría comprobar siempre esos parámetros para NULL, y usaría empty() para comprobar "sin valor". –

+0

Reintroducir NULL es falso. ¿Por qué estás usando referencias en primer lugar si necesitas el espantoso puntero NULL? En realidad, una de las razones más importantes para usar referencias es que puede estar seguro de que NO ES NULO. – Thorsten79

Respuesta

20

Si desea que el tipo sea nulo, lo convierten en un puntero. Pase punteros de cadena en lugar de referencias, ya que esto es precisamente lo que pueden hacer los punteros, y las referencias no. Las referencias siempre apuntan al mismo objeto válido. Los punteros se pueden establecer como nulos o se vuelven a colocar para apuntar a otro objeto. Por lo tanto, si necesita las cosas que los punteros pueden hacer, use punteros.

Como alternativa, use boost :: optional, que permite una forma más segura de escribir "esta variable puede o no contener un valor".

O, por supuesto, cambie la semántica para que use cadenas vacías en lugar de nulas, pase un parámetro bool diferente que especifique si la cadena está disponible o no, o refactorizador para que no lo necesite en primer lugar.

+0

1 por avisarme de impulso :: opcional –

1

Qué pasa si usted sólo tiene que utilizar:

void foo(const char *xinput) 
{ 
    if (xinput == NULL) { 
     // do something exceptional with this 
     return; 
    } 
    std::string input(xinput); 
    // remainder of code as usual 
} 

Sí, esto incurre en una asignación adicional y copia, y la llamada a la función es un poco más detallado, porque es necesario utilizar .c_str() en el caso usual, pero te da la semántica que quieres.

11

En lo personal, me gustaría cambiar la semántica para pasar alrededor vacías std :: cuerdas en lugar de NULL:

void foo(const std::string& input) 
{ 
    if (!input.empty()) 
     std::cout << input.size() << std::endl; 
} 

void bar() 
{ 
     foo(""); 
} 
+1

Dos puntos: primero, una cadena vacía puede ser un valor válido, separado de nulo. En segundo lugar, se considera una mejor idea de usar 'vacía()' en lugar de 'tamaño()' cuando lo único que importa es si el tamaño es distinto de cero. 'impulso :: optional', o punteros, son una mejor solución. –

+0

Modificado para usar empty(). Un comentario sobre la pregunta original apunta a usar NULL para indicar "no sé el valor" y vaciar para indicar "valor vacío". Lo que hace que esta respuesta sea incorrecta. Pero echaría un segundo vistazo a las cosas, ya que al pasar punteros aparece "¿a quién pertenece este puntero?" problemas. –

+0

Cualquiera boost :: opcional o un puntero inteligente sería la mejor opción en ese caso. –

2

¿Por qué no sobrecarga la función y le da a la segunda sobrecarga ningún argumento? Entonces ambas sobrecargas pueden llamar internamente a una función que proporciona la lógica de lectura y que, a su vez, pasa un puntero a std::string.

void foo_impl(string const* pstr) { … } 

void foo(string const& str) { 
    foo_impl(&str); 
} 

void foo() { 
    foo_impl(0); 
} 
11

sobrecarga de funciones al rescate ...

void foo(const std::string& input) 
{ 
    std::cout << input << std::endl; 

    // do more things ... 
} 

void foo(const char* input) 
{ 
    if (input != NULL) foo(std::string(input)); 
} 

Esto aceptará ambas matrices de char c-estilo y std :: cuerdas, e incurrirá en una carga extra en la pila si se pasa en una cadena literal o una matriz char, pero le permite mantener su implementación en un solo lugar y mantener su buena sintaxis.

+1

yo no haría esto, podría ser engañosa. – sudarkoff

+0

Ese es el punto, realmente; es una abstracción El uso debe ser sencillo, a menos que esté extremadamente preocupado por el rendimiento. – eplawless

+1

@sudarkoff: No estoy de acuerdo. El problema en sí es engañoso y necesita una abstracción. La solución es elegante. En cuanto al problema de rendimiento, "Optimización prematura, etc." ... +1. – paercebal

3

O, la mezcla de un poco de dos respuestas anteriores:

void fooImpl(const char* input) 
{ 
    if (input != NULL) 
     std::cout << input << std::endl; 
} 

void foo(const std::string& input) 
{ 
    fooImpl(input.c_str());  
} 

void foo(const char* input) 
{ 
    fooImpl(input); 
} 

misma interfaz, ninguna copia en la pila. Podrías, si quisieras, alinear fooImpl también.

2

Absolutamente no heredan de std::string. La herencia es el acoplamiento más apretado que puede tener en C++, y que sólo está buscando nulabilidad, que se puede obtener simplemente con const char*, sobrecargas, o simplemente std::string * si realmente quiere.

Cuestiones relacionadas