Pregunta

Estoy trabajando con una herramienta UNIX de código abierto implementada en C++ y necesito cambiar algo de código para que haga lo que quiero.Me gustaría hacer el cambio más pequeño posible con la esperanza de que mi parche sea aceptado en sentido ascendente.Se prefieren las soluciones que se pueden implementar en C++ estándar y que no crean más dependencias externas.

Aquí está mi problema.Tengo una clase C++ (llamémosla "A") que actualmente usa fprintf() para imprimir sus estructuras de datos muy formateadas en un puntero de archivo.En su función de impresión, también llama de forma recursiva a funciones de impresión idénticamente definidas de varias clases miembro ("B" es un ejemplo).Hay otra clase C que tiene un miembro std::string "foo" que debe configurarse en los resultados de print() de una instancia de A.Piense en ello como una función 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 cuantos menos cambios de código sean necesarios, mejor.También es necesario conservar la interfaz de impresión actual (ARCHIVO* F).He considerado varios enfoques para implementar A::to_str(), cada uno con ventajas y desventajas:

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

    • No tendría que reescribir ninguna cadena de formato.
    • print() podría reimplementarse como:fprint(f, this.to_str());
    • Pero necesitaría asignar manualmente char[]s, fusionar muchas cadenas c y, finalmente, convertir la matriz de caracteres en std::string
  2. Intente 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 reescribirse porque, que yo sepa, no existe una forma estándar de crear un flujo de salida desde un identificador de archivo UNIX (aunque este tipo dice que puede ser posible).
  3. Usa la cadena de Boost biblioteca de formatos

    • Más dependencias externas.Qué asco.
    • La sintaxis del formato es lo suficientemente diferente a la de printf() como para resultar molesta:

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

  4. Utilice Qt QString::asprintf()

    • Una dependencia externa diferente.

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

Gracias.

¿Fue útil?

Solución

Estoy usando el número 3:la biblioteca de formatos de cadenas de impulso, pero debo admitir que nunca he tenido ningún problema con las diferencias en las especificaciones de formato.

Funciona de maravilla para mí, y las dependencias externas podrían ser peores (una biblioteca muy estable)

Editado:agregando un ejemplo de cómo usar boost::format 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 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 de boost::format

He usado boost::format como reemplazo de sprintf/printf en 4 o 5 aplicaciones (escribiendo cadenas formateadas en archivos o salidas personalizadas en archivos de registro) y nunca tuve problemas con las diferencias de formato.Puede que haya algunos especificadores de formato (más o menos oscuros) que sean diferentes, pero nunca tuve ningún problema.

En contraste, tenía algunas especificaciones de formato que realmente no podía hacer con las transmisiones (por lo que recuerdo)

Otros consejos

Este es el modismo que me gusta para hacer que la funcionalidad sea idéntica a 'sprintf', pero que devuelva un std::string e inmune a los problemas de desbordamiento del 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 quieran.

#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 con C99 y que Windows (así como el glibc anterior) tenía un comportamiento vsnprintf diferente, en el que devuelve -1 en caso de error, en lugar de una medida definitiva de cuánto espacio se necesita. .Aquí está mi código revisado. ¿Podrían todos revisarlo? Si creen que está bien, lo editaré nuevamente 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];
    }
}

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

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::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, deberías usar snprintf en lugar de sprintf para evitar desbordamientos del búfer.También puede cambiar selectivamente los sprintfs más riesgosos al formato <<, para ser más seguro y, sin embargo, cambiar lo menos posible.

Deberías 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 formatos de cadenas de boost, pero mantiene la sintaxis de las funciones printf(...).

¡Espero que esto ayude!

¿Se trata de serialización?¿O imprimir correctamente?Si es lo primero, considere boost::serialization también.Se trata de serialización "recursiva" de objetos y subobjetos.

La biblioteca {fmt} proporciona fmt::sprintf función que realiza printf-formato compatible (incluidos argumentos posicionales según especificación POSIX) y devuelve el resultado como std::string:

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

Descargo de responsabilidad:Soy el autor de esta biblioteca.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top