2008-09-16 10 views
17

Estoy trabajando con una herramienta de código abierto de UNIX que se implementa en C++, y tengo que cambiar el código para que haga lo que yo querer. Me gustaría hacer el menor cambio posible con la esperanza de que mi parche sea aceptado en la fase inicial. Se prefieren las soluciones que son implementables en C++ estándar y no crean más dependencias externas.C++: cómo obtener los resultados de fprintf como un std :: string sin sprintf

Aquí está mi problema. Tengo una clase de C++, llamémosla "A", que actualmente usa fprintf() para imprimir sus estructuras de datos con gran formato a un puntero de archivo. En su función de impresión, también llama recursivamente a las funciones de impresión definidas de forma idéntica de varias clases miembro ("B" es un ejemplo). Hay otra clase C que tiene un miembro std :: string "foo" que debe establecerse en los resultados print() de una instancia de A. Piense en ello como una función de miembro to_str() para A.

en pseudocódigo:

class A { 
public: 
    ... 

    void print(FILE* f); 
    B b; 

    ... 
}; 

... 

void A::print(FILE *f) 
{ 
    std::string s = "stuff"; 
    fprintf(f, "some %s", s); 
    b.print(f); 
} 

class C { 
    ... 
    std::string foo; 
    bool set_foo(std::str); 
    ... 
} 

... 

A a = new A(); 
C c = new C(); 

... 

// wish i knew how to write A's to_str() 
c.set_foo(a.to_str()); 

debo mencionar que C es bastante estable, pero a y B (y el resto de las personas dependientes de a) se encuentran en un estado de flujo, por lo que los cambios necesarios cuanto mejor sea la menos código. La interfaz de impresión actual (ARCHIVO * F) también debe conservarse. He considerado varios enfoques para implementar A :: to_str(), cada uno con sus ventajas y desventajas:

  1. Cambiar las llamadas a fprintf() para sprintf()

    • yo no tendría que reescriba cualquier cadena de formato
    • print() podría volver a implementarse como: fprint (f, this.to_str());
    • Pero yo tendría que asignar manualmente char [] s, combinar una gran cantidad de cadenas de C, y finalmente convertir la matriz de caracteres a un std :: string
  2. tratar de atrapar los resultados de A.Print() en una corriente de cadena

    • tendría que convertir todas las cadenas de formato a formato < < salida. Hay cientos de fprintf() s para convertir: - {
    • print() debería volverse a escribir porque no hay una forma estándar que sepa de crear una secuencia de salida desde un manejador de archivo UNIX (aunque this guy says it may be possible).
  3. Usar cadena de Boost format library

    • Más dependencias externas. Yuck. sintaxis
    • de formato es lo suficientemente diferente de printf() para ser molesto:

    printf (format_str, args) -> cout < < impulso :: formato (format_str)% arg1% arg2% etc

  4. QString::asprintf()

    • Una dependencia externa diferente de

      uso de Qt.

Por lo tanto, tienen que agotado todas las posibilidades? Si es así, ¿cuál crees que es mi mejor apuesta? Si no, ¿qué he pasado por alto?

Gracias.

+0

Aunque ya respondí esta pregunta, también me gustaría señalar este proyecto: https://github.com/c42f/tinyformat, que resuelve el problema muy bien, y realmente hace un gran trabajo al reproducir la notación de formato de impresión. En estos días, uso ese paquete directamente, en lugar del método vsprintf que detallé hace unos años en mi otra respuesta. –

Respuesta

12

estoy usando # 3: la biblioteca de formato de cadena impulso - pero tengo que admitir que nunca he tenido ningún problema con las diferencias en las especificaciones de formato.

funciona como un encanto para mí - y las dependencias externas podrían ser peores (una biblioteca muy estable)

Editado: la adición de un ejemplo de cómo utilizar impulso :: formato en lugar de printf:

sprintf(buffer, "This is a string with some %s and %d numbers", "strings", 42); 

sería algo como esto con la biblioteca de impulso :: formato:

string = boost::str(boost::format("This is a string with some %s and %d numbers") %"strings" %42); 

Espero que esto ayude a aclarar el uso de impulso :: formato

He usado impulso :: formato como un reemplazo sprintf/printf en 4 o 5 aplicaciones (escribir cadenas con formato de archivos, o de salida personalizado a los archivos de registro) y nunca tuvo problemas con las diferencias de formato. Puede haber algunos (más o menos oscuros) especificadores de formato que son diferentes, pero nunca tuve un problema.

En cambio tuve algunas especificaciones de formato realmente no podía hacer con las corrientes (tanto como yo recuerdo)

+0

Gracias por la aclaración sobre el uso de boost :: format. Es tentador dado que este proyecto ya depende de otra biblioteca de impulso, pero no creo que haya nada que supere una impresión que solo funciona con std :: string como parece hacer Loki. – underspecified

+0

Probé SafeFormat de Loki, pero resulta que está intercambiando 5s de boost por() s. En una nota positiva, mi código funcionó una vez que adopté el formato boost ::-) – underspecified

+0

Me alegra oír que el formato boost :: funcionó para ti, nunca probé el método Loki. – bernhardrusch

0

¿Se trata de serialización? O la impresión adecuada? Si es el primero, considere boost :: serialization también. Se trata de la serialización "recursiva" de objetos y subobjetos.

+0

Se trata de la impresión adecuada. C.foo es una pieza de datos que finalmente se muestra al usuario (en gran parte) tal como está. Si este fuera mi código, perdería el sinsentido de impresión (ARCHIVO *) que es demasiado restrictivo. – underspecified

1

Puede utilizar std :: string y iostreams con formato, como la llamada setw() y otros en iomanip

+0

Gracias por informarme sobre iomanip. Ciertamente parece útil, pero quiero intentar preservar las numerosas cadenas de formato existentes tanto como sea posible. – underspecified

35

Aquí está el idioma que me gusta para hacer funcionalidad idéntica a 'sprintf', pero volviendo un std :: string, e inmune a los problemas de desbordamiento de búfer. Este código es parte de un proyecto de código abierto que estoy escribiendo (licencia BSD), por lo que todos pueden usarlo como lo deseen.

#include <string> 
#include <cstdarg> 
#include <vector> 
#include <string> 

std::string 
format (const char *fmt, ...) 
{ 
    va_list ap; 
    va_start (ap, fmt); 
    std::string buf = vformat (fmt, ap); 
    va_end (ap); 
    return buf; 
} 



std::string 
vformat (const char *fmt, va_list ap) 
{ 
    // Allocate a buffer on the stack that's big enough for us almost 
    // all the time. 
    size_t size = 1024; 
    char buf[size]; 

    // Try to vsnprintf into our buffer. 
    va_list apcopy; 
    va_copy (apcopy, ap); 
    int needed = vsnprintf (&buf[0], size, fmt, ap); 
    // NB. On Windows, vsnprintf returns -1 if the string didn't fit the 
    // buffer. On Linux & OSX, it returns the length it would have needed. 

    if (needed <= size && needed >= 0) { 
     // It fit fine the first time, we're done. 
     return std::string (&buf[0]); 
    } else { 
     // vsnprintf reported that it wanted to write more characters 
     // than we allotted. So do a malloc of the right size and try again. 
     // This doesn't happen very often if we chose our initial size 
     // well. 
     std::vector <char> buf; 
     size = needed; 
     buf.resize (size); 
     needed = vsnprintf (&buf[0], size, fmt, apcopy); 
     return std::string (&buf[0]); 
    } 
} 

EDIT: cuando escribí este código, no tenía ni idea de que esto requiere la conformidad C99 y que Windows (así como glibc más) tuvo un comportamiento diferente vsnprintf, en el que se devuelve -1 para el fracaso, en lugar de una medida definitiva de cuánto espacio se necesita. Aquí está mi código revisado, podría todo el mundo que lo analice y si usted piensa que está bien, voy a editar de nuevo para hacer que el único coste aparece:

std::string 
Strutil::vformat (const char *fmt, va_list ap) 
{ 
    // Allocate a buffer on the stack that's big enough for us almost 
    // all the time. Be prepared to allocate dynamically if it doesn't fit. 
    size_t size = 1024; 
    char stackbuf[1024]; 
    std::vector<char> dynamicbuf; 
    char *buf = &stackbuf[0]; 
    va_list ap_copy; 

    while (1) { 
     // Try to vsnprintf into our buffer. 
     va_copy(ap_copy, ap); 
     int needed = vsnprintf (buf, size, fmt, ap); 
     va_end(ap_copy); 

     // NB. C99 (which modern Linux and OS X follow) says vsnprintf 
     // failure returns the length it would have needed. But older 
     // glibc and current Windows return -1 for failure, i.e., not 
     // telling us how much was needed. 

     if (needed <= (int)size && needed >= 0) { 
      // It fit fine so we're done. 
      return std::string (buf, (size_t) needed); 
     } 

     // vsnprintf reported that it wanted to write more characters 
     // than we allotted. So try again using a dynamic buffer. This 
     // doesn't happen very often if we chose our initial size well. 
     size = (needed > 0) ? (needed+1) : (size*2); 
     dynamicbuf.resize (size); 
     buf = &dynamicbuf[0]; 
    } 
} 
+0

Buen trabajo en formato/vformat. Tal vez stackoverflow necesita algún tipo de sección de intercambio de snippit de código :-) – underspecified

+1

Parece que su código no funcionará si la cadena resultante es más grande que 1024 bytes. Según MSDN: vsnprintf - Valor devuelto ... si el número de caracteres para escribir es mayor que el recuento, estas funciones devuelven -1 indicando que la salida se ha truncado. – Andreas

+1

¿Por qué usar un tamaño de matriz variable si no vas a hacer esto en un bucle? No es necesario copiar 'ap', que podría ser costoso. La página man de printf de GNU [v] s [n] tiene un ejemplo de cómo hacerlo de forma más portátil: http://www.tin.org/bin/man.cgi?section=3&topic=snprintf – Bklyn

1

El siguiente podría ser una solución alternativa:

void A::printto(ostream outputstream) { 
    char buffer[100]; 
    string s = "stuff"; 
    sprintf(buffer, "some %s", s); 
    outputstream << buffer << endl; 
    b.printto(outputstream); 
} 

(B::printto similar), y definir

void A::print(FILE *f) { 
    printto(ofstream(f)); 
} 

string A::to_str() { 
    ostringstream os; 
    printto(os); 
    return os.str(); 
} 

Por supuesto, realmente debería usar snprintf en lugar de sprintf para evitar desbordamientos de búfer. También puede cambiar de forma selectiva los sprintfs más arriesgados al formato < <, para estar más seguros y, sin embargo, cambiar lo menos posible.

+0

Tomaré su respuesta como una combinación de # 1 y # 2 :-) ¿Ofstream tiene un constructor que toma los identificadores de archivo? Tenía la impresión de que eran incompatibles ... – underspecified

+0

@underspecified: tenías razón. – smerlin

1

Debe probar el archivo de encabezado SafeFormat de la biblioteca Loki (http://loki-lib.sourceforge.net/index.php?n=Idioms.Printf). Es similar a la biblioteca de formato de cadena de boost, pero mantiene la sintaxis de las funciones printf (...).

Espero que esto ayude!

+0

Por favor, no solo publique alguna herramienta o biblioteca como respuesta. Al menos demuestre [cómo resuelve el problema] (http://meta.stackoverflow.com/a/251605) en la respuesta misma. –

Cuestiones relacionadas