it-swarm-es.com

C ++: Cómo obtener los resultados de FPRintF como una STD :: String w / O Sprintf

Estoy trabajando con una herramienta de unix de código abierto que se implementa en C++, y necesito cambiar un 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 sentido ascendente. Las soluciones que se implementan en la norma C++ y no se prefieren más dependencias externas.

Aquí está mi problema. Tengo una clase de C++: lo llamamos "A", que actualmente utiliza FPRINTF () para imprimir sus estructuras de datos muy formateadas a un puntero de archivos. En su función de impresión, también llama recursivamente las funciones de impresión de forma idéntica de varias clases miembros ("B" es un ejemplo). Hay otra clase C que tiene un miembro STD :: String "Foo" que debe configurarse en los resultados de impresión () de una instancia de A. Piense en él como una función miembro de TOU_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());

Debería mencionar que C es bastante estable, pero A y B (y el resto de los dependientes de A) están en un estado de flujo, por lo que los cambios de menor código necesarios son necesarios. La interfaz de impresión actual (archivo * f) también debe ser preservada. He considerado varios enfoques para implementar un :: TO_STR (), cada uno con ventajas y desventajas:

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

    • No tendría que reescribir ninguna cadena de formato.
    • imprimir () podría reimplicarse como: FIPT (F, este.to_str ());
    • Pero necesitaría asignar manualmente charar [] s, fusionaron muchas cuerdas C y finalmente convertir la matriz de caracteres a una cadena STD ::
  2. Trate de capturar los resultados de A.Print () en un flujo de cadena

    • Tendría que convertir todas las cadenas de formato en << Formato de salida. Hay cientos de FPRINTF () S para convertir: - {
    • imprimir () tendría que ser reescrito porque no existe una forma estándar que conozca para crear un flujo de salida desde un mango de archivo UNIX (aunque este tipo dice que puede ser posible ).
  3. Usa la cadena de BOOST Biblioteca de formato

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

    printF (Format_str, Args) -> COUT << BOOST :: FORMATO (FORMAT_STR)% ARG1% ARG2% ETC

  4. Utilice 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.

18
underspecified

Aquí está el idioma que me gusta para hacer la funcionalidad idéntica a 'SPRIRTF', pero devolviendo un STD :: String, e inmune a los problemas de desbordamiento de buffer. Este código forma parte de un proyecto de código abierto que estoy escribiendo (licencia BSD), para que todos se sientan libres de usar esto como desee.

#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 tuve idea de que esto requirió la conformidad de C99 y que las ventanas (así como la GLIBC más antigua) tengan diferentes comportamientos de VSNPRINTF, en los que devuelve -1 por falla, en lugar de una medida definitiva de cuánto espacio es necesario. Aquí está mi código revisado, ¿podría todos buscarlo y, si cree que está bien, volveré a editar para que el único costo indique:

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];
    }
}
39
Larry Gritz