C ++: cómo obtener resultados de fprintf como std :: string sin sprintf

votos
17

Estoy trabajando con una herramienta de código abierto de UNIX que se implementa en C ++, y necesito cambiar el código para que haga lo que quiero. 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.

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 los dependientes de A) están en un estado de cambio, por lo que cuanto menos cambios de código sean necesarios, mejor. La interfaz de impresión actual (ARCHIVO * F) también debe conservarse. He considerado varios enfoques para implementar A :: to_str (), cada uno con ventajas y desventajas:

  1. Cambia las llamadas a fprintf () a sprintf ()

    • No tendría que volver a escribir cadenas de formato
    • print () podría volver a implementarse como: fprint (f, this.to_str ());
    • Pero necesitaría asignar manualmente char [] s, fusionar muchas cadenas c y finalmente convertir la matriz de caracteres a std :: string
  2. Intenta capturar los resultados de a.print () en una secuencia de cadenas

    • Tendría que convertir todas las cadenas de formato al formato de salida <<. Hay cientos de fprintf () s para convertir: - {
    • print () tendría que ser reescrito porque no hay una forma estándar que sepa de crear una secuencia de salida desde un manejador de archivo UNIX (aunque este tipo dice que puede ser posible ).
  3. Utilice la biblioteca de formato de cadena de Boost

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

    printf (format_str, args) -> cout << boost :: format (format_str)% arg1% arg2% etc

  4. Usa Qt's QString :: asprintf ()

    • Una dependencia externa diferente.

Entonces, ¿he agotado todas las opciones posibles? Si es así, ¿cuál crees que es mi mejor apuesta? Si no, ¿qué he pasado por alto?

Gracias.

Publicado el 16/09/2008 a las 05:12
fuente por usuario
En otros idiomas...                            


7 respuestas

votos
0

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

Respondida el 16/09/2008 a las 05:18
fuente por usuario

votos
13

Estoy utilizando el n. ° 3: la biblioteca de formato de cadena de impulso, pero debo 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: agregando un ejemplo de cómo usar el formato boost :: en lugar de printf:

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

sería algo así con la biblioteca boost :: format:

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 del formato boost ::

Utilicé el formato boost :: como un reemplazo sprintf / printf en 4 o 5 aplicaciones (escribiendo cadenas formateadas en archivos, o salida personalizada a archivos de registro) y nunca tuve 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.

Por el contrario, tenía algunas especificaciones de formato que realmente no podía hacer con las transmisiones (tanto como recuerdo)

Respondida el 16/09/2008 a las 05:18
fuente por usuario

votos
1

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

Respondida el 16/09/2008 a las 05:19
fuente por usuario

votos
35

Aquí está el modismo que me gusta para hacer que la funcionalidad sea idéntica a 'sprintf', pero devuelve una cadena std :: e inmune a los problemas de desbordamiento del buffer. 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]);
    }
}

EDITAR: cuando escribí este código, no tenía idea de que esto requería conformidad C99 y de que Windows (al igual que glibc anterior) tenía un comportamiento vsnprintf diferente, en el que devolvía -1 por falla, en lugar de una medida definitiva de cuánto espacio es necesario. Aquí está mi código revisado, ¿podría todo el mundo revisarlo y si cree que está bien, lo editaré de nuevo para que sea el único costo enumerado?

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];
    }
}
Respondida el 16/09/2008 a las 05:52
fuente por usuario

votos
1

La 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::printtosimilar) y define

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 peligrosos al formato <<, para estar más seguros y, sin embargo, cambiar lo menos posible.

Respondida el 16/09/2008 a las 05:53
fuente por usuario

votos
1

Debería 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!

Respondida el 16/09/2008 a las 07:00
fuente por usuario

votos
0

La biblioteca {fmt} proporciona fmt::sprintffunción que realiza printfel formato compatible (incluyendo argumentos posicionales de acuerdo con la especificación POSIX ) y devuelve el resultado como std::string:

std::string s = fmt::sprintf("The answer is %d.", 42);

Exención de responsabilidad : yo soy el autor de esta biblioteca.

Respondida el 23/05/2019 a las 03:42
fuente por usuario

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more