2009-11-12 21 views
8

Estoy trabajando en C++ con dos grandes piezas de código, una hecha en "estilo C" y otra en "estilo C++".C++ std :: string y NULL const char *

El código de tipo C tiene funciones que devuelven el código C++ const char * y ha en lugares numerosas cosas como

const char* somecstylefunction(); 
... 
std::string imacppstring = somecstylefunction(); 

donde se está construyendo la cadena de un const char * devuelto por el código de estilo C .

Esto funcionó hasta que cambió el código de estilo C y comenzó a devolver punteros NULL a veces. Esto, por supuesto, provoca fallas seg.

Hay un montón de código alrededor y por lo tanto me gustaría la forma más parsimoniosa de solucionar este problema. El comportamiento esperado es que imacppstring sería la cadena vacía en este caso. ¿Hay una buena y elegante solución para esto?

actualización

La const char * devuelto por estas funciones son siempre punteros a cadenas estáticas. Se usaron principalmente para pasar mensajes informativos (destinados a la registración más probable) sobre cualquier comportamiento inesperado en la función. Se decidió que el tener estos NULL retorno de la "nada que informar" estaba muy bien, porque entonces se podría utilizar el valor de retorno como un condicional, es decir

if (somecstylefunction()) do_something; 

mientras que antes las funciones devuelven la cadena estática "";

Si esto fue una buena idea, no voy a tocar este código y no depende de mí de todos modos.

Lo que quería evitar era rastrear cada inicialización de cadena para agregar una función de envoltura.

+0

Me gustaría mencionar que usted es "mantenedor", como lo mencionó en un comentario, podría estar rompiendo algunas reglas de estilo bastante importantes. No sé por qué querrías devolver un puntero a un char a menos que se pase a la función a través del puntero. ¿Podrías profundizar en esto? Es posible que tenga problemas mayores. –

+1

¿Alguien puede responder por qué la implementación std :: basic_string no trata un puntero value_type nulo como una cadena vacía? – jmucchiello

+0

@jmucchiello, creo que porque, fundamentalmente, una "cadena vacía" es muy diferente de un puntero que apunta a la dirección de memoria 0. Una cadena vacía '" "' en realidad ocupa memoria como un único carácter nulo. –

Respuesta

13

Probablemente lo mejor que se puede hacer es arreglar las funciones de la biblioteca C con su comportamiento de cambio previo. pero tal vez no tienes control sobre esa biblioteca.

El segundo aspecto a considerar es cambiar todos los casos en que está en función de las funciones lib C regresan una cadena vacía para utilizar una función de contenedor que va a 'arreglar' los punteros NULL:

const char* nullToEmpty(char const* s) 
{ 
    return (s ? s : ""); 
} 

Así que ahora

std::string imacppstring = somecstylefunction(); 

podría ser:

std::string imacppstring(nullToEmpty(somecstylefunction()); 

Si eso es inaceptable (i puede ser un trabajo muy ocupado, pero debería ser un cambio mecánico único), podría implementar una biblioteca 'paralela' que tenga los mismos nombres que la biblioteca en C que está utilizando actualmente, con esas funciones simplemente llamando al funciones C lib originales y fijación de punteros NULL según corresponda. Tendría que jugar algunos juegos complicados con encabezados, el enlazador y/o los espacios de nombres C++ para hacer que esto funcione, y esto tiene un gran potencial para causar confusión en el camino, así que pensaría mucho antes de ir por ese camino. .

Pero algo como lo siguiente podría empezar:

// .h file for a C++ wrapper for the C Lib 
namespace clib_fixer { 
    const char* somecstylefunction(); 
} 


// .cpp file for a C++ wrapper for the C Lib 
namespace clib_fixer { 
    const char* somecstylefunction() { 
     const char* p = ::somecstylefunction(); 

     return (p ? p : ""); 
    } 
} 

Ahora sólo hay que añadir que la cabecera a.cpp que reclaman actualmente llamando a las funciones lib C (y probablemente eliminar la cabecera de la lib C) y añadir un

using namespace clib_fixer; 

al archivo .cpp utilizando esas funciones.

Eso podría no ser demasiado malo. Tal vez.

+0

"y probablemente elimine el encabezado de la biblioteca C" - Estoy bastante seguro de que tiene que eliminarlo. De lo contrario, la llamada a 'somecstylefunction' será ambigua entre' namespace clib_fixer' y 'namespace ::'. –

3

Supongo que podría simplemente agregar una función de envoltura que prueba NULL, y devuelve una cadena std :: vacía. Pero, lo que es más importante, ¿por qué tus funciones C ahora devuelven NULL? ¿Qué indica un puntero NULL? Si indica un error grave, es posible que desee que su función contenedora genere una excepción.

O para estar seguro, solo puede verificar NULL, manejar la caja NULL, y solo luego construir una std :: string.

const char* s = somecstylefunction(); 
if (!s) explode(); 
std::string str(s); 
+0

En este caso, no indica error. El responsable decidió que era más limpio que "mensaje" se indicara con un puntero nulo en lugar de un puntero al "" estático. Lo cual no es irrazonable, pero rompe este otro código. –

+0

En ese caso, simplemente podría usar una función de contenedor en línea que prueba NULL, y devuelve una std :: string vacía si el puntero es NULL. –

0

Algo así debería solucionar su problema.

const char *cString; 
std::string imacppstring; 

cString = somecstylefunction(); 
if (cString == NULL) { 
    imacppstring = ""; 
} else { 
    imacppstring = cString; 
} 

Si lo desea, puede pegar la lógica de comprobación de errores en su propia función. Tendría que poner este bloque de código en menos lugares, entonces.

6

Bueno, sin cambiar cada lugar donde se inicializa C++ std::string directamente desde una llamada a función C (para agregar la verificación de puntero nulo), la única solución sería prohibir que las funciones C devuelvan punteros nulos.

En compilador GCC, se puede utilizar un compilador de extensión "condicionales con Operandos omitidos" para crear una macro contenedor para su función C

#define somecstylefunction() (somecstylefunction() ? : "") 

pero en caso general Aconsejaría contra de eso.

+0

Sí, parece algo de lo que me gustaría alejarme. Probablemente sea mejor solucionar el origen del problema que los síntomas. –

+0

Alguna forma de macro o un conjunto de sus propios envoltorios alrededor de la biblioteca sería una buena manera de resolver el problema sin cambiar la biblioteca original. – Anton

+1

@antonmarkov: Sí, pero la combinación de la extensión del compilador macro trickery * y * es un poco demasiado en mi opinión. – AnT

1

No suelo recomendar subclases de contenedores estándar, pero en este caso podría funcionar.

class mystring : public std::string 
{ 
    // ... appropriate constructors are an exercise left to the reader 
    mystring & operator=(const char * right) 
    { 
     if (right == NULL) 
     { 
      clear(); 
     } 
     else 
     { 
      std::string::operator=(right); // I think this works, didn't check it... 
     } 
     return *this; 
    } 
}; 
+0

¿Aún crees que es una buena idea? – Walter

+0

@Walter, nunca dije que fuera una * buena * idea, solo una manera de sacar lo mejor de una mala situación. Y funciona: http://ideone.com/2xGd1A –

+0

Con C++ 11, para que la semántica de movimiento funcione entre 'std :: string' y' mystring' (en cualquier dirección), qué otros miembros (aparte de los constructores) ¿necesito sobrecargar? ¿No necesito una conversión rvalue a 'std :: string &&'? – Walter

2

Para una solución portátil:

(a) definir su propio tipo de cadena. La parte más importante es la búsqueda y el reemplazo de todo el proyecto, que puede ser simple si siempre es std :: string o un gran dolor de una sola vez. (Me gustaría hacer la única consulta de que es Liskov-sustituible para std :: string, pero también construye una cadena vacía de un carácter nulo *.

La implementación más fácil es heredar públicamente de std :: string. eso está mal visto (por razones comprensibles), estaría bien en este caso, y también ayuda con bibliotecas de terceros que esperan un std::string, así como herramientas de depuración. Alternativamente, aggegate y reenvía - yuck

(b) # define std :: string como tu propio tipo de cadena. Arriesgado, no recomendado. No lo haría a menos que conociera muy bien las bases de código y ahorraras toneladas de trabajo (y agregaría algunas renuncias para proteger los restos de mi reputación;))

(c) He trabajado en algunos de estos casos al redefinir el tipo ofensivo en alguna clase de utilidad solo para el propósito de la inclusión (por lo que el #define tiene un alcance mucho más limitado). Sin embargo, no tengo idea de cómo hacer eso para un char *.

(d) Escriba un contenedor de importación. Si los encabezados de la biblioteca C tienen un diseño bastante regular, y/o conoce a alguien que tenga experiencia en el análisis del código C++, es posible que pueda generar un "encabezado contenedor".

(e) solicite al propietario de la biblioteca que configure el valor "Cadena nula" configurable al menos en tiempo de compilación. (Una solicitud aceptable, ya que cambiar a 0 puede romper la compatibilidad también en otros escenarios) ¡Incluso podría ofrecerse a enviar el cambio usted mismo si eso no le resulta tan útil!

2

Usted puede envolver todas las llamadas a funciones C-stlye en algo como esto ...

std::string makeCppString(const char* cStr) 
{ 
    return cStr ? std::string(cStr) : std::string(""); 
} 

A continuación, siempre que tengas:

std::string imacppstring = somecstylefunction(); 

sustituirlo por:

std::string imacppstring = makeCppString(somecystylefunction()); 

Por supuesto, esto supone que construir una cadena vacía es un comportamiento aceptable cuando su función devuelve NULL.

+0

'std :: string imacppstring = makeCppString (somecystylefunction);' no funcionará porque está pasando la dirección de la función, no se devuelve 'char const *'. –

Cuestiones relacionadas