std :: formato de cadena como sprintf


454

Tengo a formato std::stringcon sprintfy enviarlo a secuencia de archivo. ¿Cómo puedo hacer esto?


66
uso breve de la historia larga boost::format(como la solución de kennytm usa aquí ). boost::format¡Ya es compatible con los operadores de transmisión C ++ también! ejemplo: cout << format("helloworld. a=%s, b=%s, c=%s") % 123 % 123.123 % "this is a test" << endl;. boost::formattiene la menor cantidad de líneas de código ... es revisado por pares y se integra muy bien con las secuencias de C ++.
Trevor Boyd Smith

@Ockonal: por el bien de la comunidad (no podría importarme menos mi representante), le sugiero que cambie su selección. El seleccionado actualmente, en el primer fragmento, presenta un error a la espera de suceder en el uso de una longitud máxima arbitraria. El segundo fragmento ignora por completo su deseo declarado de usar vargs como sprintf. Le sugiero que seleccione la ÚNICA respuesta aquí que sea limpia, segura, que solo se base en estándares C ++, probados y bien comentados. Que sea mío no es relevante. Es objetivamente cierto. Ver stackoverflow.com/questions/2342162/… .
Douglas Daseeco

@TrevorBoydSmith a std::formatse agregó a C ++ 20 BTW: stackoverflow.com/a/57286312/895245 ¡Impresionante!
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

1
@CiroSantilli ¡Leí un artículo sobre C++20ayer y vi que se C++20copió boost(por enésima vez) agregando el std::formata la C++20especificación! Estaba muy muy feliz! Se han utilizado casi todos los archivos C ++ que he escrito en los últimos 9 años boost::format. agregar una salida oficial de estilo printf a las transmisiones en C ++ será de gran ayuda IMO para todo C ++.
Trevor Boyd Smith el

Respuestas:


333

No puede hacerlo directamente, porque no tiene acceso de escritura al búfer subyacente (hasta C ++ 11; vea el comentario de Dietrich Epp ). Tendrás que hacerlo primero en una cadena en C, luego copiarlo en una cadena estándar:

  char buff[100];
  snprintf(buff, sizeof(buff), "%s", "Hello");
  std::string buffAsStdStr = buff;

Pero no estoy seguro de por qué no usarías una secuencia de cadena. Supongo que tiene razones específicas para no solo hacer esto:

  std::ostringstream stringStream;
  stringStream << "Hello";
  std::string copyOfStr = stringStream.str();

17
La galleta mágica char buf[100];hace que esta solución no sea muy robusta. Pero la idea esencial está ahí.
John Dibling

18
John, las corrientes no son lentas. La única razón por la que los flujos parecen lentos es que, por defecto, los iostreams se sincronizan con la salida C FILE, de modo que la salida intercalada e printfs se emiten correctamente. Deshabilitar este enlace (con una llamada a cout.sync_with_stdio (false)) hace que las transmisiones de c ++ superen a stdio, al menos a partir de MSVC10.
Jimbo

72
La razón para usar formatos es permitir que un localizador reconstruya la estructura de la oración para idiomas extranjeros, en lugar de codificar la gramática de la oración.
Martijn Courteaux

216
Por alguna razón, otros lenguajes usan una sintaxis similar a printf: Java, Python (la nueva sintaxis aún está más cerca de printf que de streams). Solo C ++ inflige esta abominación detallada a seres humanos inocentes.
quant_dev

99
Aún mejor, use asprintf, que asigna una nueva cadena con suficiente espacio para contener el resultado. Luego cópielo a un std::stringsi lo desea, y recuerde freeel original. Además, es posible poner esto en una macro para que cualquier buen compilador ayude a validar el formato para usted; no desea poner un lugar doubledonde %sse espera que sea
Aaron McDaid

286

C ++ moderno hace que esto sea súper simple.

C ++ 20

C ++ 20 introduce std::format, lo que le permite hacer exactamente eso. Utiliza campos de reemplazo similares a los de Python :

#include <iostream>
#include <format>

int main() {
    std::cout << std::format("Hello {}!\n", "world");
}

¡Mira la documentación completa ! Es una gran mejora en la calidad de vida.


C ++ 11

Con C ++ 11 s std::snprintf, esto ya se convirtió en una tarea bastante fácil y segura.

#include <memory>
#include <string>
#include <stdexcept>

template<typename ... Args>
std::string string_format( const std::string& format, Args ... args )
{
    size_t size = snprintf( nullptr, 0, format.c_str(), args ... ) + 1; // Extra space for '\0'
    if( size <= 0 ){ throw std::runtime_error( "Error during formatting." ); }
    std::unique_ptr<char[]> buf( new char[ size ] ); 
    snprintf( buf.get(), size, format.c_str(), args ... );
    return std::string( buf.get(), buf.get() + size - 1 ); // We don't want the '\0' inside
}

El fragmento de código anterior está licenciado bajo CC0 1.0 .

Explicación línea por línea:

Objetivo: escribir en achar*usando std::snprintfy luego convertir eso en astd::string.

Primero, determinamos la longitud deseada de la matriz de caracteres usando una condición especial en snprintf. Desde cppreference.com :

Valor de retorno

[...] Si la cadena resultante se trunca debido al límite de tamaño buf_size, la función devuelve el número total de caracteres (sin incluir el byte nulo de terminación) que se habrían escrito si no se impusiera el límite.

Esto significa que el tamaño deseado es el número de caracteres más uno , de modo que el terminador nulo se ubicará después de todos los demás caracteres y que el constructor de cadenas puede cortarlo nuevamente. Este problema fue explicado por @ alexk7 en los comentarios.

size_t size = snprintf( nullptr, 0, format.c_str(), args ... ) + 1;

snprintfdevolverá un número negativo si se produjo un error, por lo que luego verificamos si el formato funcionó como se desea. No hacer esto podría conducir a errores silenciosos o la asignación de un gran búfer, como lo señala @ead en los comentarios.

if( size <= 0 ){ throw std::runtime_error( "Error during formatting." ); }

A continuación, asignamos una nueva matriz de caracteres y la asignamos a a std::unique_ptr. Esto generalmente se recomienda, ya que no tendrá deleteque volver a hacerlo manualmente .

Tenga en cuenta que esta no es una forma segura de asignar un unique_ptrtipo definido por el usuario, ya que no puede desasignar la memoria si el constructor arroja una excepción.

std::unique_ptr<char[]> buf( new char[ size ] );

Después de eso, por supuesto, podemos usarlo snprintfpara su uso previsto y escribir la cadena formateada en el char[].

snprintf( buf.get(), size, format.c_str(), args ... );

Finalmente, creamos y devolvemos uno nuevo a std::stringpartir de eso, asegurándonos de omitir el terminador nulo al final.

return std::string( buf.get(), buf.get() + size - 1 );

Puedes ver un ejemplo en acción aquí .


Si también desea usar std::stringen la lista de argumentos, eche un vistazo a esta esencia .


Información adicional para usuarios de Visual Studio :

Como se explica en esta respuesta , Microsoft renombró std::snprintfa _snprintf(sí, sin std::). MS lo configuró como obsoleto y recomienda usarlo _snprintf_sen su lugar, sin embargo _snprintf_s, no aceptará que el búfer sea cero o menor que la salida formateada y no calculará la longitud de las salidas si eso ocurre. Entonces, para deshacerse de las advertencias de desaprobación durante la compilación, puede insertar la siguiente línea en la parte superior del archivo que contiene el uso de _snprintf:

#pragma warning(disable : 4996)

Pensamientos finales

Muchas respuestas a esta pregunta se escribieron antes de la época de C ++ 11 y usan longitudes de búfer fijas o vargs. A menos que esté atascado con versiones antiguas de C ++, no recomendaría usar esas soluciones. Idealmente, siga el camino C ++ 20.

Debido a que la solución C ++ 11 en esta respuesta usa plantillas, puede generar bastante código si se usa mucho. Sin embargo, a menos que esté desarrollando para un entorno con un espacio muy limitado para binarios, esto no será un problema y sigue siendo una gran mejora con respecto a las otras soluciones, tanto en claridad como en seguridad.

Si la eficiencia del espacio es muy importante, estas dos soluciones con vargs y vsnprintf pueden ser útiles. NO UTILICE ninguna solución con longitudes de búfer fijas, eso es solo pedir problemas.


2
Haga hincapié en su respuesta para los usuarios de Visual Studio de que la versión de VS debe ser al menos 2013. En este artículo, puede ver que solo funciona con la versión VS2013: si el búfer es un puntero nulo y el recuento es cero, len se devuelve como el recuento de caracteres necesarios para formatear la salida, sin incluir la terminación nula. Para realizar una llamada exitosa con el mismo argumento y parámetros de configuración regional, asigne un búfer que contenga al menos len + 1 caracteres.
cha

3
@moooeeeep Múltiples razones. En primer lugar, el objetivo aquí es devolver un std :: string, no un c-string, por lo que probablemente quiso return string(&buf[0], size);decir algo similar. En segundo lugar, si tuviera que devolver una cadena en C como esa, causaría un comportamiento indefinido porque el vector que contiene los valores a los que apunta se invalidará al regresar. En tercer lugar, cuando comencé a aprender C ++, el estándar no definía en qué orden debían almacenarse los elementos dentro de un std::vector, por lo que acceder a su almacenamiento a través de un puntero era un comportamiento indefinido. Ahora funcionaría, pero no veo ningún beneficio en hacerlo de esa manera.
iFreilicht

2
@iFreilicht Se construirá un nuevo std::stringa partir del vector convertido implícitamente ( inicialización de copia ), que luego se devuelve como una copia, como sugiere la firma de la función. Además, los elementos de a std::vectorson, y siempre fueron destinados a ser, almacenados contiguamente . Pero entiendo que tal vez no sea beneficioso hacerlo.
moooeeeep

44
Realmente me gusta esta solución, sin embargo, creo que la línea return string(buf.get(), buf.get() + size);debería ser, de lo return string(buf.get(), buf.get() + size - 1);contrario, obtendrás una cadena con un carácter nulo al final. Encontré que este es el caso en gcc 4.9.
Phil Williams

3
Al pasar una cadena std :: a% s se produce un error de compilación ( error: no se puede pasar el objeto de tipo no trivial 'std :: __ cxx11 :: basic_string <char>' a través de la función variadic; la llamada se cancelará en tiempo de ejecución [-Wnon-pod -varargs] ) en clang 3.9.1, pero en CL 19 se compila bien y se bloquea en tiempo de ejecución. ¿Algún indicador de advertencia que pueda activar para tener esa llamada en tiempo de compilación en cl también?
Zitrax

241

Solución C ++ 11 que utiliza vsnprintf()internamente:

#include <stdarg.h>  // For va_start, etc.

std::string string_format(const std::string fmt, ...) {
    int size = ((int)fmt.size()) * 2 + 50;   // Use a rubric appropriate for your code
    std::string str;
    va_list ap;
    while (1) {     // Maximum two passes on a POSIX system...
        str.resize(size);
        va_start(ap, fmt);
        int n = vsnprintf((char *)str.data(), size, fmt.c_str(), ap);
        va_end(ap);
        if (n > -1 && n < size) {  // Everything worked
            str.resize(n);
            return str;
        }
        if (n > -1)  // Needed size returned
            size = n + 1;   // For null char
        else
            size *= 2;      // Guess at a larger size (OS specific)
    }
    return str;
}

Un enfoque más seguro y más eficiente (lo probé y es más rápido):

#include <stdarg.h>  // For va_start, etc.
#include <memory>    // For std::unique_ptr

std::string string_format(const std::string fmt_str, ...) {
    int final_n, n = ((int)fmt_str.size()) * 2; /* Reserve two times as much as the length of the fmt_str */
    std::unique_ptr<char[]> formatted;
    va_list ap;
    while(1) {
        formatted.reset(new char[n]); /* Wrap the plain char array into the unique_ptr */
        strcpy(&formatted[0], fmt_str.c_str());
        va_start(ap, fmt_str);
        final_n = vsnprintf(&formatted[0], n, fmt_str.c_str(), ap);
        va_end(ap);
        if (final_n < 0 || final_n >= n)
            n += abs(final_n - n + 1);
        else
            break;
    }
    return std::string(formatted.get());
}

El fmt_strse pasa por valor para cumplir con los requisitos de va_start.

NOTA: La versión "más segura" y "más rápida" no funciona en algunos sistemas. Por lo tanto, ambos todavía están en la lista. Además, "más rápido" depende por completo de que el paso de preasignación sea correcto, de lo contrario, lo strcpyhace más lento.


3
lento. ¿Por qué aumentar el tamaño en 1? ¿Y cuándo devuelve esta función -1?
0x DEAD BEEF

27
¿Está sobrescribiendo str.c_str ()? ¿No es eso peligroso?
Quantum

8
va_start con un argumento de referencia tiene problemas en MSVC. Falla en silencio y devuelve punteros a la memoria aleatoria. Como solución alternativa, use std :: string fmt en lugar de std :: string & fmt, o escriba un objeto contenedor.
Steve Hanov

66
I + 1 porque sé que esto probablemente funcionará en función de cómo se implementen la mayoría de las cadenas std ::, sin embargo, c_str no pretende ser un lugar para modificar la cadena subyacente. Se supone que es de solo lectura.
Doug T.

66
Y para obtener la longitud de cadena resultante de antemano, consulte: stackoverflow.com/a/7825892/908336 No veo el punto de aumentar sizeen cada iteración, cuando puede obtenerlo por la primera llamada de vsnprintf().
Massood Khaari

107

boost::format() proporciona la funcionalidad que desea:

A partir de la sinopsis de las bibliotecas de formato Boost:

Un objeto de formato se construye a partir de una cadena de formato y luego recibe argumentos a través de llamadas repetidas al operador%. Cada uno de esos argumentos se convierte en cadenas, que a su vez se combinan en una cadena, de acuerdo con la cadena de formato.

#include <boost/format.hpp>

cout << boost::format("writing %1%,  x=%2% : %3%-th try") % "toto" % 40.23 % 50; 
// prints "writing toto,  x=40.230 : 50-th try"

55
también puede eliminar las bibliotecas que necesita. Usando una herramienta suministrada.
Hassan Syed

77
Boost Format no solo es grande, sino que también es muy lento. Ver zverovich.net/2013/09/07/… y boost.org/doc/libs/1_52_0/libs/spirit/doc/html/spirit/karma/…
vitaut

14
La inclusión de impulso en cualquier parte de su proyecto aumenta inmediatamente los tiempos de compilación de manera significativa. Para proyectos grandes, lo más probable es que no importe. Para proyectos pequeños, el impulso es un lastre.
quant_dev

2
Si bien se @vitaut terriblemente consumo de recursos en comparación con las alternativas. ¿Con qué frecuencia formatea cadenas? Teniendo en cuenta que solo lleva unos pocos microsegundos y que la mayoría de los proyectos probablemente solo lo usan unas pocas docenas de veces, no se nota en un proyecto que no se centra mucho en el formato de cadenas, ¿verdad?
AturSams

2
Desafortunadamente, boost :: format no funciona de la misma manera: no acepta var_args. A algunas personas les gusta tener todo el código relacionado con un solo programa con el mismo aspecto / utilizando los mismos modismos.
xor007

88

C ++ 20 incluirá lo std::formatque se asemeja sprintfen términos de API pero es totalmente seguro para los tipos, funciona con tipos definidos por el usuario y usa una sintaxis de cadena de formato similar a Python. Así es como podrá formatearlo std::stringy escribirlo en una secuencia:

std::string s = "foo";
std::cout << std::format("Look, a string: {}", s);

o

std::string s = "foo";
puts(std::format("Look, a string: {}", s).c_str());

Alternativamente, puede usar la biblioteca {fmt} para formatear una cadena y escribirla en stdoutuna secuencia de archivos de una vez:

fmt::print(f, "Look, a string: {}", s); // where f is a file stream

En cuanto a la sprintfmayoría de las otras respuestas aquí, desafortunadamente usan varargs y son inherentemente inseguras a menos que use algo como el formatatributo de GCC que solo funciona con cadenas de formato literal. Puede ver por qué estas funciones no son seguras en el siguiente ejemplo:

std::string format_str = "%s";
string_format(format_str, format_str[0]);

donde string_formates una implementación de la respuesta de Erik Aronesty. Este código se compila, pero lo más probable es que se bloquee cuando intente ejecutarlo:

$ g++ -Wall -Wextra -pedantic test.cc 
$ ./a.out 
Segmentation fault: 11

Descargo de responsabilidad : soy el autor de {fmt} y C ++ 20 std::format.


En mi humilde opinión, te pierdes la inclusión error: 'fmt' has not been declared
Sérgio

Esto es solo un fragmento, no un código completo. Obviamente, debe incluir <fmt / format.h> y poner el código en una función.
vitaut

para mí no es tan obvio, en mi humilde opinión, debe incluirlo en el fragmento, gracias por los comentarios
Sérgio

1
¡Se fmtagregó una implementación similar a C ++ 20! stackoverflow.com/a/57286312/895245 fmt actualmente reclama soporte para ello. ¡Impresionante trabajo!
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

2
@vitaut ¡Gracias por tu trabajo en esto!
Curt Nichols


15

Escribí el mío usando vsnprintf para que devuelva una cadena en lugar de tener que crear mi propio búfer.

#include <string>
#include <cstdarg>

//missing string printf
//this is safe and convenient but not exactly efficient
inline std::string format(const char* fmt, ...){
    int size = 512;
    char* buffer = 0;
    buffer = new char[size];
    va_list vl;
    va_start(vl, fmt);
    int nsize = vsnprintf(buffer, size, fmt, vl);
    if(size<=nsize){ //fail delete buffer and try again
        delete[] buffer;
        buffer = 0;
        buffer = new char[nsize+1]; //+1 for /0
        nsize = vsnprintf(buffer, size, fmt, vl);
    }
    std::string ret(buffer);
    va_end(vl);
    delete[] buffer;
    return ret;
}

Entonces puedes usarlo como

std::string mystr = format("%s %d %10.5f", "omg", 1, 10.5);

Esto hace una copia extra completa de los datos, es posible usar vsnprintfdirectamente en la cadena.
Mooing Duck

1
Use el código en stackoverflow.com/a/7825892/908336 para obtener la longitud de cadena resultante de antemano. Y puede usar punteros inteligentes para un código seguro de excepción:std::unique_ptr<char[]> buffer (new char[size]);
Massood Khaari

No estoy seguro de que esto sea correcto en el caso de reserva; Creo que necesita hacer una va_copy de vl para el segundo vsnprintf () para ver los argumentos correctamente. Para ver un ejemplo, consulte: github.com/haberman/upb/blob/…
Josh Haberman el

15

Para formatear std::stringde manera 'sprintf', llame a snprintf(argumentos nullptry 0) para obtener la longitud de búfer necesaria. Escriba su función usando una plantilla variada de C ++ 11 como esta:

#include <cstdio>
#include <string>
#include <cassert>

template< typename... Args >
std::string string_sprintf( const char* format, Args... args ) {
  int length = std::snprintf( nullptr, 0, format, args... );
  assert( length >= 0 );

  char* buf = new char[length + 1];
  std::snprintf( buf, length + 1, format, args... );

  std::string str( buf );
  delete[] buf;
  return str;
}

Compile con soporte C ++ 11, por ejemplo en GCC: g++ -std=c++11

Uso:

  std::cout << string_sprintf("%g, %g\n", 1.23, 0.001);

std :: snprintf no está disponible en VC ++ 12 (Visual Studio 2013). Reemplácelo con _snprintf en su lugar.
Shital Shah

¿Por qué no lo usas en char buf[length + 1];lugar de char* buf = new char[length + 1];?
Behrouz.M

La diferencia entre usar char[]y char*con nuevo, es que en el primer caso se asignaría buf en la pila. Está bien para búferes pequeños, pero como no podemos garantizar el tamaño de la cadena resultante, es un poco mejor usarlo new. Por ejemplo, en mi máquina string_sprintf("value: %020000000d",5), imprimo un número escandaloso de ceros a la izquierda antes del número 5, los volcados de núcleo cuando se usa la matriz en la pila, pero funciona bien cuando se usa la matriz asignada dinámicamentenew char[length + 1]
usuario 2622016

idea muy inteligente para obtener el tamaño de beneficio real necesario para la salida formateada
Chris

1
@ user2622016: ¡Gracias por la solución! Tenga en cuenta que std::move es superfluo .
Mihai Todor

14

[editar: 20/05/25] mejor aún ...:
En el encabezado:

// `say` prints the values
// `says` returns a string instead of printing
// `sayss` appends the values to it's first argument instead of printing
// `sayerr` prints the values and returns `false` (useful for return statement fail-report)<br/>

void PRINTSTRING(const std::string &s); //cater for GUI, terminal, whatever..
template<typename...P> void say(P...p) { std::string r{}; std::stringstream ss(""); (ss<<...<<p); r=ss.str(); PRINTSTRING(r); }
template<typename...P> std::string says(P...p) { std::string r{}; std::stringstream ss(""); (ss<<...<<p); r=ss.str(); return r; }
template<typename...P> void sayss(std::string &s, P...p) { std::string r{}; std::stringstream ss(""); (ss<<...<<p); r=ss.str();  s+=r; } //APPENDS! to s!
template<typename...P> bool sayerr(P...p) { std::string r{}; std::stringstream ss("ERROR: "); (ss<<...<<p); r=ss.str(); PRINTSTRING(r); return false; }

La PRINTSTRING(r)función es atender la GUI o terminal o cualquier salida especial que se necesite #ifdef _some_flag_, el valor predeterminado es:

void PRINTSTRING(const std::string &s) { std::cout << s << std::flush; }

[edit '17 / 8/31] Añadiendo una versión variada con plantilla 'vtspf (..)':

template<typename T> const std::string type_to_string(const T &v)
{
    std::ostringstream ss;
    ss << v;
    return ss.str();
};

template<typename T> const T string_to_type(const std::string &str)
{
    std::istringstream ss(str);
    T ret;
    ss >> ret;
    return ret;
};

template<typename...P> void vtspf_priv(std::string &s) {}

template<typename H, typename...P> void vtspf_priv(std::string &s, H h, P...p)
{
    s+=type_to_string(h);
    vtspf_priv(s, p...);
}

template<typename...P> std::string temp_vtspf(P...p)
{
    std::string s("");
    vtspf_priv(s, p...);
    return s;
}

que es efectivamente una versión delimitada por comas (en su lugar) de los <<operadores a veces obstaculizadores , utilizados de esta manera:

char chSpace=' ';
double pi=3.1415;
std::string sWorld="World", str_var;
str_var = vtspf("Hello", ',', chSpace, sWorld, ", pi=", pi);


[editar] Adaptado para hacer uso de la técnica en la respuesta de Erik Aronesty (arriba):

#include <string>
#include <cstdarg>
#include <cstdio>

//=============================================================================
void spf(std::string &s, const std::string fmt, ...)
{
    int n, size=100;
    bool b=false;
    va_list marker;

    while (!b)
    {
        s.resize(size);
        va_start(marker, fmt);
        n = vsnprintf((char*)s.c_str(), size, fmt.c_str(), marker);
        va_end(marker);
        if ((n>0) && ((b=(n<size))==true)) s.resize(n); else size*=2;
    }
}

//=============================================================================
void spfa(std::string &s, const std::string fmt, ...)
{
    std::string ss;
    int n, size=100;
    bool b=false;
    va_list marker;

    while (!b)
    {
        ss.resize(size);
        va_start(marker, fmt);
        n = vsnprintf((char*)ss.c_str(), size, fmt.c_str(), marker);
        va_end(marker);
        if ((n>0) && ((b=(n<size))==true)) ss.resize(n); else size*=2;
    }
    s += ss;
}

[respuesta anterior]
Una respuesta muy tardía, pero para aquellos que, como yo, les gusta la forma 'sprintf': he escrito y estoy usando las siguientes funciones. Si te gusta, puedes expandir las% -options para que se ajusten más a las sprintf; los que hay actualmente son suficientes para mis necesidades. Utiliza stringf () y stringfappend () igual que usaría sprintf. Solo recuerda que los parámetros para ... deben ser tipos POD.

//=============================================================================
void DoFormatting(std::string& sF, const char* sformat, va_list marker)
{
    char *s, ch=0;
    int n, i=0, m;
    long l;
    double d;
    std::string sf = sformat;
    std::stringstream ss;

    m = sf.length();
    while (i<m)
    {
        ch = sf.at(i);
        if (ch == '%')
        {
            i++;
            if (i<m)
            {
                ch = sf.at(i);
                switch(ch)
                {
                    case 's': { s = va_arg(marker, char*);  ss << s;         } break;
                    case 'c': { n = va_arg(marker, int);    ss << (char)n;   } break;
                    case 'd': { n = va_arg(marker, int);    ss << (int)n;    } break;
                    case 'l': { l = va_arg(marker, long);   ss << (long)l;   } break;
                    case 'f': { d = va_arg(marker, double); ss << (float)d;  } break;
                    case 'e': { d = va_arg(marker, double); ss << (double)d; } break;
                    case 'X':
                    case 'x':
                        {
                            if (++i<m)
                            {
                                ss << std::hex << std::setiosflags (std::ios_base::showbase);
                                if (ch == 'X') ss << std::setiosflags (std::ios_base::uppercase);
                                char ch2 = sf.at(i);
                                if (ch2 == 'c') { n = va_arg(marker, int);  ss << std::hex << (char)n; }
                                else if (ch2 == 'd') { n = va_arg(marker, int); ss << std::hex << (int)n; }
                                else if (ch2 == 'l') { l = va_arg(marker, long);    ss << std::hex << (long)l; }
                                else ss << '%' << ch << ch2;
                                ss << std::resetiosflags (std::ios_base::showbase | std::ios_base::uppercase) << std::dec;
                            }
                        } break;
                    case '%': { ss << '%'; } break;
                    default:
                    {
                        ss << "%" << ch;
                        //i = m; //get out of loop
                    }
                }
            }
        }
        else ss << ch;
        i++;
    }
    va_end(marker);
    sF = ss.str();
}

//=============================================================================
void stringf(string& stgt,const char *sformat, ... )
{
    va_list marker;
    va_start(marker, sformat);
    DoFormatting(stgt, sformat, marker);
}

//=============================================================================
void stringfappend(string& stgt,const char *sformat, ... )
{
    string sF = "";
    va_list marker;
    va_start(marker, sformat);
    DoFormatting(sF, sformat, marker);
    stgt += sF;
}

@MooingDuck: se modificó el parámetro de la función según el comentario de Dan a la respuesta de Aronesty. Solo uso Linux / gcc, y con fmtreferencia funciona bien. (Pero supongo que la gente querrá jugar con juguetes, así que ...) Si hay algún otro supuesto "error", ¿podría explicarlo?
slashmais

No entendí cómo funcionaba parte de su código y pensé que estaba haciendo muchos cambios de tamaño. Reexaminar muestra que me equivoqué. Tu código es correcto
Mooing Duck

A partir de la respuesta de Erik Aronesty hay un arenque rojo. Su primer ejemplo de código no es seguro y su segundo es ineficiente y torpe. La implementación limpia se indica claramente por el hecho de que, si el buf_siz de cualquiera de las funciones de la familia vprintf es cero, no se escribe nada y el búfer puede ser un puntero nulo, sin embargo, el valor de retorno (número de bytes que se escribirían sin incluir el terminador nulo) todavía se calcula y se devuelve. Una respuesta de calidad de producción está aquí: stackoverflow.com/questions/2342162/…
Douglas Daseeco

10

Así es como lo hace google: StringPrintf(Licencia BSD)
y Facebook lo hace de una manera bastante similar: StringPrintf(Licencia Apache)
Ambas también ofrecen una opción conveniente StringAppendF.


10

Mis dos centavos en esta pregunta muy popular.

Para citar la página de manual de printffunciones similares :

Tras un retorno exitoso, estas funciones devuelven el número de caracteres impresos (excluyendo el byte nulo utilizado para finalizar la salida a las cadenas).

Las funciones snprintf () y vsnprintf () no escriben más de bytes de tamaño (incluido el byte nulo de terminación ('\ 0')). Si la salida se trunca debido a este límite, el valor de retorno es el número de caracteres (excluyendo el byte nulo de terminación) que se habrían escrito en la cadena final si hubiera suficiente espacio disponible. Por lo tanto, un valor de retorno de tamaño o más significa que la salida se truncó.

En otras palabras, una implementación sensata de C ++ 11 debería ser la siguiente:

#include <string>
#include <cstdio>

template <typename... Ts>
std::string fmt (const std::string &fmt, Ts... vs)
{
    char b;
    size_t required = std::snprintf(&b, 0, fmt.c_str(), vs...) + 1;
        // See comments: the +1 is necessary, while the first parameter
        //               can also be set to nullptr

    char bytes[required];
    std::snprintf(bytes, required, fmt.c_str(), vs...);

    return std::string(bytes);
}

Funciona bastante bien :)

Las plantillas variables solo se admiten en C ++ 11. La respuesta de pixelpoint muestra una técnica similar usando estilos de programación más antiguos.

Es extraño que C ++ no tenga tal cosa fuera de la caja. Recientemente agregaron to_string(), lo que en mi opinión es un gran paso adelante. Me pregunto si agregarán un .formatoperador al std::stringeventualmente ...

Editar

Como señaló alexk7, +1se necesita A en el valor de retorno de std::snprintf, ya que necesitamos tener espacio para el \0byte. Intuitivamente, en la mayoría de las arquitecturas que faltan +1, el requiredentero se sobrescribirá parcialmente con a 0. Esto sucederá después de la evaluación de requiredcomo parámetro real para std::snprintf, por lo que el efecto no debe ser visible.

Sin embargo, este problema podría cambiar, por ejemplo, con la optimización del compilador: ¿qué sucede si el compilador decide usar un registro para la requiredvariable? Este es el tipo de errores que a veces resultan en problemas de seguridad.


1
snprintf siempre agrega un byte nulo final pero devuelve el número de caracteres sin él. ¿Este código no omite siempre el último carácter?
alexk7

@ alexk7, buena captura! Estoy actualizando la respuesta. El código no omite el último carácter, sino que escribe más allá del final del bytesbúfer, probablemente sobre el requiredentero (que afortunadamente en ese momento ya está evaluado).
Dacav

1
Solo una pequeña pista: con un tamaño de búfer de 0, puede pasar a nullptrcomo argumento de búfer, eliminando la char b;línea en su código. ( Fuente )
iFreilicht

@iFreilicht, arreglado. También +1
Dacav

2
El uso de "char bytes [requerido]" se asignará en la pila en lugar del montón, puede ser peligroso en cadenas de gran formato. Considere usar un nuevo en su lugar. Yann
Yannuth

9
template<typename... Args>
std::string string_format(const char* fmt, Args... args)
{
    size_t size = snprintf(nullptr, 0, fmt, args...);
    std::string buf;
    buf.reserve(size + 1);
    buf.resize(size);
    snprintf(&buf[0], size + 1, fmt, args...);
    return buf;
}

Uso de C99 snprintf y C ++ 11


9

Probado, respuesta de calidad de producción

Esta respuesta maneja el caso general con técnicas que cumplen con los estándares. El mismo enfoque se da como ejemplo en CppReference.com cerca de la parte inferior de su página. A diferencia de su ejemplo, este código se ajusta a los requisitos de la pregunta y se prueba en campo en robótica y aplicaciones satelitales. También ha mejorado los comentarios. La calidad del diseño se analiza más adelante.

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

// requires at least C++11
const std::string vformat(const char * const zcFormat, ...) {

    // initialize use of the variable argument array
    va_list vaArgs;
    va_start(vaArgs, zcFormat);

    // reliably acquire the size
    // from a copy of the variable argument array
    // and a functionally reliable call to mock the formatting
    va_list vaArgsCopy;
    va_copy(vaArgsCopy, vaArgs);
    const int iLen = std::vsnprintf(NULL, 0, zcFormat, vaArgsCopy);
    va_end(vaArgsCopy);

    // return a formatted string without risking memory mismanagement
    // and without assuming any compiler or platform specific behavior
    std::vector<char> zc(iLen + 1);
    std::vsnprintf(zc.data(), zc.size(), zcFormat, vaArgs);
    va_end(vaArgs);
    return std::string(zc.data(), iLen); }

#include <ctime>
#include <iostream>
#include <iomanip>

// demonstration of use
int main() {

    std::time_t t = std::time(nullptr);
    std::cerr
        << std::put_time(std::localtime(& t), "%D %T")
        << " [debug]: "
        << vformat("Int 1 is %d, Int 2 is %d, Int 3 is %d", 11, 22, 33)
        << std::endl;
    return 0; }

Eficiencia lineal predecible

Dos pases son necesarios para una función reutilizable segura, confiable y predecible según las especificaciones de la pregunta. Las presunciones sobre la distribución de tamaños de vargs en una función reutilizable es un mal estilo de programación y deben evitarse. En este caso, las representaciones arbitrariamente grandes de longitud variable de vargs son un factor clave en la elección del algoritmo.

Volver a intentar el desbordamiento es exponencialmente ineficiente, que es otra razón discutida cuando el comité de estándares C ++ 11 discutió la propuesta anterior para proporcionar una ejecución en seco cuando el búfer de escritura es nulo.

En la implementación lista para producción anterior, la primera ejecución es tan seca para determinar el tamaño de asignación. No se produce asignación. El análisis de las directivas printf y la lectura de vargs se ha hecho extremadamente eficiente durante décadas. El código reutilizable debe ser predecible, incluso si se debe sacrificar una pequeña ineficiencia para casos triviales.

Seguridad y confiabilidad

Andrew Koenig dijo a un pequeño grupo de nosotros después de su conferencia en un evento de Cambridge: "Las funciones del usuario no deberían depender de la explotación de una falla para una funcionalidad excepcional". Como de costumbre, su sabiduría se ha demostrado verdadera en el registro desde entonces. Los problemas de errores de seguridad fijos y cerrados a menudo indican que se han vuelto a intentar hacks en la descripción del agujero explotado antes de la corrección.

Esto se menciona en la propuesta formal de revisión de estándares para la función de búfer nulo en Alternativa a sprintf, Propuesta de revisión C9X , Documento ISO IEC WG14 N645 / X3J11 96-008 . Una cadena arbitrariamente larga insertada por directiva de impresión, "% s", dentro de las restricciones de la disponibilidad de memoria dinámica, no es una excepción, y no debe explotarse para producir, "Funcionalidad no excepcional".

Considere la propuesta junto con el código de ejemplo que figura en la parte inferior de la página C ++ Reference.org vinculada al primer párrafo de esta respuesta.

Además, la prueba de casos de falla rara vez es tan sólida como la de los casos de éxito.

Portabilidad

Todos los principales proveedores de sistemas operativos proporcionan compiladores que son totalmente compatibles con std :: vsnprintf como parte de los estándares de c ++ 11. Los hosts que ejecutan productos de proveedores que ya no mantienen distribuciones deben estar provistos de g ++ o clang ++ por muchas razones.

Uso de pila

El uso de la pila en la primera llamada a std :: vsnprintf será menor o igual que el de la segunda, y se liberará antes de que comience la segunda llamada. Si la primera llamada excede la disponibilidad de la pila, std :: fprintf también fallará.


Breve y robusto. Podría fallar en HP-UX, IRIX, Tru64 que tienen vsnprintf-s no conformes. EDITAR: también, considerando cómo los dos pases pueden afectar el rendimiento, esp. para el formato más común de cadenas pequeñas, ¿ha considerado una suposición para el paso inicial, que podría ser lo suficientemente grande?
Ingeniero

FWIW, la suposición a la que me refería usa un búfer asignado por pila donde ocurre la primera ejecución. Si se ajusta, ahorra el costo de una segunda ejecución y la asignación dinámica que ocurre allí. Presumiblemente, las cadenas pequeñas se usan con más frecuencia que las cadenas grandes. En mi punto de referencia crudo, esa estrategia (casi) reduce a la mitad el tiempo de ejecución para cadenas pequeñas y está dentro de unos pocos porcentajes (¿quizás una sobrecarga fija?) De la estrategia anterior. ¿Podría elaborar el diseño C ++ 11 que emplea una ejecución en seco, etc.? Me gustaría leer al respecto.
Ingeniero

@ Ingeniero, sus preguntas se han abordado en el cuerpo de la respuesta, arriba y debajo del código. Los subtemas pueden hacerse más fáciles de leer de esa manera.
Douglas Daseeco

6

C ++ 20 std::format

¡Ha llegado! La característica se describe en: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0645r9.html y utiliza una .format()sintaxis similar a Python .

Espero que el uso sea como:

#include <format>
#include <string>

int main() {
    std::string message = std::format("The answer is {}.", 42);
}

Lo intentaré cuando llegue el soporte a GCC, GCC 9.1.0 con g++-9 -std=c++2atodavía no lo admite.

La API agregará un nuevo std::formatencabezado:

La API de formato propuesta se define en el nuevo encabezado <format>y no debería tener ningún impacto en el código existente.

La fmtbiblioteca existente afirma implementarlo si necesita el polyfill: https://github.com/fmtlib/fmt

Implementación de C ++ 20 std::format.

y se mencionó anteriormente en: std :: formato de cadena como sprintf


5

Según la respuesta proporcionada por Erik Aronesty:

std::string string_format(const std::string &fmt, ...) {
    std::vector<char> str(100,'\0');
    va_list ap;
    while (1) {
        va_start(ap, fmt);
        auto n = vsnprintf(str.data(), str.size(), fmt.c_str(), ap);
        va_end(ap);
        if ((n > -1) && (size_t(n) < str.size())) {
            return str.data();
        }
        if (n > -1)
            str.resize( n + 1 );
        else
            str.resize( str.size() * 2);
    }
    return str.data();
}

Esto evita la necesidad de rechazar constel resultado del .c_str()cual estaba en la respuesta original.


1
A partir de la respuesta de Erik Aronesty hay un arenque rojo. Su primer ejemplo de código no es seguro y el segundo, con el bucle, es ineficiente y torpe. La implementación limpia se indica claramente por el hecho de que, si el buf_siz de cualquiera de las funciones de la familia vprintf es cero, no se escribe nada y el búfer puede ser un puntero nulo, sin embargo, el valor de retorno (número de bytes que se escribirían sin incluir el terminador nulo) todavía se calcula y se devuelve. Una respuesta de calidad de producción está aquí: stackoverflow.com/questions/2342162/…
Douglas Daseeco

La respuesta de Erik Aronesty ha sido editada desde que se agregó la mía. Quería resaltar la opción de usar el vector <char> para almacenar cadenas a medida que se construyen. Utilizo esta técnica a menudo cuando llamo a funciones C desde código C ++. Es interesante que la pregunta ahora tenga 34 respuestas.
ChetS

El ejemplo de cppreference.com en la página vfprintf se agregó más tarde. Creo que la mejor respuesta es la respuesta actualmente aceptada, el uso de secuencias de cadenas en lugar de una variante printf es la forma de hacer las cosas en C ++. Sin embargo, mi respuesta agregó valor cuando fue proporcionada; Fue incrementalmente mejor que otras respuestas en ese momento. Ahora el estándar tiene string_view, paquetes de parámetros y plantilla Variadic, una nueva respuesta podría incluir esas características. En cuanto a mi respuesta, aunque puede que ya no merezca votos positivos adicionales, no merece ser eliminada o rechazada, así que lo dejo como está.
ChetS

5
inline void format(string& a_string, const char* fmt, ...)
{
    va_list vl;
    va_start(vl, fmt);
    int size = _vscprintf( fmt, vl );
    a_string.resize( ++size );
    vsnprintf_s((char*)a_string.data(), size, _TRUNCATE, fmt, vl);
    va_end(vl);
}

1
+1 para la idea inteligente, pero no está muy claro qué _vscprintfes. Creo que deberías dar más detalles sobre esta respuesta.
Dacav

3

string no tiene lo que necesita, pero std :: stringstream sí. Use un flujo de cadena para crear la cadena y luego extraiga la cadena. Aquí hay una lista completa de las cosas que puede hacer. Por ejemplo:

cout.setprecision(10); //stringstream is a stream like cout

le dará 10 decimales de precisión al imprimir un doble o flotante.


8
que todavía no te da nada cerca del control que printf te da ... pero es agradable
Erik Aronesty

3

Podrías probar esto:

string str;
str.resize( _MAX_PATH );

sprintf( &str[0], "%s %s", "hello", "world" );
// optionals
// sprintf_s( &str[0], str.length(), "%s %s", "hello", "world" ); // Microsoft
// #include <stdio.h>
// snprintf( &str[0], str.length(), "%s %s", "hello", "world" ); // c++11

str.resize( strlen( str.data() ) + 1 );

3

Si está en un sistema que tiene asprintf (3) , puede envolverlo fácilmente:

#include <iostream>
#include <cstdarg>
#include <cstdio>

std::string format(const char *fmt, ...) __attribute__ ((format (printf, 1, 2)));

std::string format(const char *fmt, ...)
{
    std::string result;

    va_list ap;
    va_start(ap, fmt);

    char *tmp = 0;
    int res = vasprintf(&tmp, fmt, ap);
    va_end(ap);

    if (res != -1) {
        result = tmp;
        free(tmp);
    } else {
        // The vasprintf call failed, either do nothing and
        // fall through (will return empty string) or
        // throw an exception, if your code uses those
    }

    return result;
}

int main(int argc, char *argv[]) {
    std::string username = "you";
    std::cout << format("Hello %s! %d", username.c_str(), 123) << std::endl;
    return 0;
}

2
Agregaría esta línea como una declaración antes format, ya que le dice a gcc que verifique los tipos de argumentos y dé una advertencia decente con -Wall:std::string format(const char *fmt, ...) __attribute__ ((format (printf, 1, 2)));
Aaron McDaid

2
Acabo de agregar una llamada a va_end. "si no se llama a va_end antes de que regrese una función que llama a va_start o va_copy, el comportamiento no está definido". - docs
Aaron McDaid

1
Debe verificar el resultado de retorno de vasprintf ya que el valor del puntero no está definido en caso de falla. Entonces, posiblemente incluya <nuevo> y agregue: if (size == -1) {throw std :: bad_alloc (); }
Neil McGill

Buen punto, modifiqué la respuesta en consecuencia, decidí poner un comentario allí en lugar de hacerlo throw std::bad_alloc();, ya que no estoy usando excepciones de C ++ en mi base de código, y para las personas que lo hacen, pueden agregarlo fácilmente basado en el comentario fuente y tu comentario aquí.
Thomas Perl

2

Este es el código que utilizo para hacer esto en mi programa ... No es nada sofisticado, pero hace el truco ... Tenga en cuenta que tendrá que ajustar su tamaño según corresponda. MAX_BUFFER para mí es 1024.

std::string Format ( const char *fmt, ... )
{
    char textString[MAX_BUFFER*5] = {'\0'};

    // -- Empty the buffer properly to ensure no leaks.
    memset(textString, '\0', sizeof(textString));

    va_list args;
    va_start ( args, fmt );
    vsnprintf ( textString, MAX_BUFFER*5, fmt, args );
    va_end ( args );
    std::string retStr = textString;
    return retStr;
}

44
La inicialización de textString ya establece todo el búfer en cero. No es necesario recordar ...
EricSchaefer

Esto hace una copia extra completa de los datos, es posible usar vsnprintfdirectamente en la cadena.
Mooing Duck

2

Tomó la idea de Dacav y la respuesta de pixelpoint . Jugué un poco y obtuve esto:

#include <cstdarg>
#include <cstdio>
#include <string>

std::string format(const char* fmt, ...)
{
    va_list vl;

    va_start(vl, fmt);
    int size = vsnprintf(0, 0, fmt, vl) + sizeof('\0');
    va_end(vl);

    char buffer[size];

    va_start(vl, fmt);
    size = vsnprintf(buffer, size, fmt, vl);
    va_end(vl);

    return std::string(buffer, size);
}

Con sana práctica de programación Creo que el código debe ser suficiente, sin embargo todavía estoy abierto a alternativas más seguras que todavía son bastante simples y no requerirían C ++ 11.


Y aquí hay otra versión que utiliza un búfer inicial para evitar una segunda llamada vsnprintf()cuando el búfer inicial ya es suficiente.

std::string format(const char* fmt, ...)
{

    va_list vl;
    int size;

    enum { INITIAL_BUFFER_SIZE = 512 };

    {
        char buffer[INITIAL_BUFFER_SIZE];

        va_start(vl, fmt);
        size = vsnprintf(buffer, INITIAL_BUFFER_SIZE, fmt, vl);
        va_end(vl);

        if (size < INITIAL_BUFFER_SIZE)
            return std::string(buffer, size);
    }

    size += sizeof('\0');

    char buffer[size];

    va_start(vl, fmt);
    size = vsnprintf(buffer, size, fmt, vl);
    va_end(vl);

    return std::string(buffer, size);
}

(Resulta que esta versión es similar a la respuesta de Piti Ongmongkolkul , solo que no usa newy delete[], y también especifica un tamaño al crear std::string.

La idea aquí de no usar newy delete[]es implicar el uso de la pila sobre el montón ya que no necesita llamar a las funciones de asignación y desasignación, sin embargo, si no se usa correctamente, podría ser peligroso desbordar el búfer en algunos (quizás viejos o quizás solo vulnerables) sistemas. Si esto es una preocupación, sugiero usar newy en su delete[]lugar. Tenga en cuenta que la única preocupación aquí es acerca de las asignaciones como vsnprintf()ya se llama con límites, por lo que especificar un límite basado en el tamaño asignado en el segundo búfer también evitaría esos).


2

Usualmente uso esto:

std::string myformat(const char *const fmt, ...)
{
        char *buffer = NULL;
        va_list ap;

        va_start(ap, fmt);
        (void)vasprintf(&buffer, fmt, ap);
        va_end(ap);

        std::string result = buffer;
        free(buffer);

        return result;
}

Desventaja: no todos los sistemas admiten vasprint


vasprintf es agradable; sin embargo, debe verificar el código de retorno. En -1 el búfer tendrá un valor indefinido. Necesidad: if (size == -1) {throw std :: bad_alloc (); }
Neil McGill

2

A continuación, la versión ligeramente modificada de la respuesta @iFreilicht, actualizada a C ++ 14 (uso de la make_uniquefunción en lugar de declaración en bruto) y soporte adicional para std::stringargumentos (basado en el artículo de Kenny Kerr )

#include <iostream>
#include <memory>
#include <string>
#include <cstdio>

template <typename T>
T process_arg(T value) noexcept
{
    return value;
}

template <typename T>
T const * process_arg(std::basic_string<T> const & value) noexcept
{
    return value.c_str();
}

template<typename ... Args>
std::string string_format(const std::string& format, Args const & ... args)
{
    const auto fmt = format.c_str();
    const size_t size = std::snprintf(nullptr, 0, fmt, process_arg(args) ...) + 1;
    auto buf = std::make_unique<char[]>(size);
    std::snprintf(buf.get(), size, fmt, process_arg(args) ...);
    auto res = std::string(buf.get(), buf.get() + size - 1);
    return res;
}

int main()
{
    int i = 3;
    float f = 5.f;
    char* s0 = "hello";
    std::string s1 = "world";
    std::cout << string_format("i=%d, f=%f, s=%s %s", i, f, s0, s1) << "\n";
}

Salida:

i = 3, f = 5.000000, s = hello world

Si lo desea, puede combinar esta respuesta con la respuesta original.



1

Puede formatear la salida de C ++ en cout usando el archivo de encabezado iomanip. Asegúrese de incluir el archivo de encabezado iomanip antes de utilizar cualquiera de las funciones auxiliares como setprecision, setfill, etc.

Aquí hay un fragmento de código que he usado en el pasado para imprimir el tiempo de espera promedio en el vector, que he "acumulado".

#include<iomanip>
#include<iostream>
#include<vector>
#include<numeric>

...

cout<< "Average waiting times for tasks is " << setprecision(4) << accumulate(all(waitingTimes), 0)/double(waitingTimes.size()) ;
cout << " and " << Q.size() << " tasks remaining" << endl;

Aquí hay una breve descripción de cómo podemos formatear flujos C ++. http://www.cprogramming.com/tutorial/iomanip.html


1

Puede haber problemas si el búfer no es lo suficientemente grande como para imprimir la cadena. Debe determinar la longitud de la cadena formateada antes de imprimir un mensaje formateado allí. Hago mi propio ayudante para esto (probado en Windows y Linux GCC ), y puedes intentar usarlo.

String.cpp: http://pastebin.com/DnfvzyKP
String.h: http://pastebin.com/7U6iCUMa

String.cpp:

#include <cstdio>
#include <cstdarg>
#include <cstring>
#include <string>

using ::std::string;

#pragma warning(disable : 4996)

#ifndef va_copy
#ifdef _MSC_VER
#define va_copy(dst, src) dst=src
#elif !(__cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__))
#define va_copy(dst, src) memcpy((void*)dst, (void*)src, sizeof(*src))
#endif
#endif

///
/// \breif Format message
/// \param dst String to store formatted message
/// \param format Format of message
/// \param ap Variable argument list
///
void toString(string &dst, const char *format, va_list ap) throw() {
  int length;
  va_list apStrLen;
  va_copy(apStrLen, ap);
  length = vsnprintf(NULL, 0, format, apStrLen);
  va_end(apStrLen);
  if (length > 0) {
    dst.resize(length);
    vsnprintf((char *)dst.data(), dst.size() + 1, format, ap);
  } else {
    dst = "Format error! format: ";
    dst.append(format);
  }
}

///
/// \breif Format message
/// \param dst String to store formatted message
/// \param format Format of message
/// \param ... Variable argument list
///
void toString(string &dst, const char *format, ...) throw() {
  va_list ap;
  va_start(ap, format);
  toString(dst, format, ap);
  va_end(ap);
}

///
/// \breif Format message
/// \param format Format of message
/// \param ... Variable argument list
///
string toString(const char *format, ...) throw() {
  string dst;
  va_list ap;
  va_start(ap, format);
  toString(dst, format, ap);
  va_end(ap);
  return dst;
}

///
/// \breif Format message
/// \param format Format of message
/// \param ap Variable argument list
///
string toString(const char *format, va_list ap) throw() {
  string dst;
  toString(dst, format, ap);
  return dst;
}


int main() {
  int a = 32;
  const char * str = "This works!";

  string test(toString("\nSome testing: a = %d, %s\n", a, str));
  printf(test.c_str());

  a = 0x7fffffff;
  test = toString("\nMore testing: a = %d, %s\n", a, "This works too..");
  printf(test.c_str());

  a = 0x80000000;
  toString(test, "\nMore testing: a = %d, %s\n", a, "This way is cheaper");
  printf(test.c_str());

  return 0;
}

String.h:

#pragma once
#include <cstdarg>
#include <string>

using ::std::string;

///
/// \breif Format message
/// \param dst String to store formatted message
/// \param format Format of message
/// \param ap Variable argument list
///
void toString(string &dst, const char *format, va_list ap) throw();
///
/// \breif Format message
/// \param dst String to store formatted message
/// \param format Format of message
/// \param ... Variable argument list
///
void toString(string &dst, const char *format, ...) throw();
///
/// \breif Format message
/// \param format Format of message
/// \param ... Variable argument list
///
string toString(const char *format, ...) throw();

///
/// \breif Format message
/// \param format Format of message
/// \param ap Variable argument list
///
string toString(const char *format, va_list ap) throw();

Con respecto a la línea vsnprintf((char *)dst.data(), dst.size() + 1, format, ap);: ¿es seguro asumir que el búfer de la cadena tiene espacio para un carácter nulo de terminación? ¿Hay implementaciones que no asignan tamaño + 1 caracteres. ¿Sería más seguro hacerlodst.resize(length+1); vsnprintf((char *)dst.data(), dst.size(), format, ap); dst.resize(length);
Drwatsoncode

Aparentemente, la respuesta a mi comentario anterior es: No, NO es seguro asumir que hay un carácter nulo. Específicamente con respecto a la especificación C ++ 98: "Acceder al valor en data () + size () produce un comportamiento indefinido : no hay garantías de que un carácter nulo termine la secuencia de caracteres señalada por el valor devuelto por esta función. Ver cadena :: c_str para una función que ofrece dicha garantía. un programa no modificará ninguno de los personajes en esta secuencia. "sin embargo, el C ++ 11 especificación indica que datay c_strson sinónimos.
drwatsoncode

1
_return.desc = (boost::format("fail to detect. cv_result = %d") % st_result).str();

1

Solución muy, muy simple.

std::string strBuf;
strBuf.resize(256);
int iCharsPrinted = sprintf_s((char *)strPath.c_str(), strPath.size(), ...);
strBuf.resize(iCharsPrinted);

1

Me doy cuenta de que esto ha sido respondido muchas veces, pero esto es más conciso:

std::string format(const std::string fmt_str, ...)
{
    va_list ap;
    char *fp = NULL;
    va_start(ap, fmt_str);
    vasprintf(&fp, fmt_str.c_str(), ap);
    va_end(ap);
    std::unique_ptr<char[]> formatted(fp);
    return std::string(formatted.get());
}

ejemplo:

#include <iostream>
#include <random>

int main()
{
    std::random_device r;
    std::cout << format("Hello %d!\n", r());
}

Ver también http://rextester.com/NJB14150


1

ACTUALIZACIÓN 1 : fmt::formatpruebas agregadas

Tomé mi propia investigación sobre los métodos que introduje aquí y obtuve resultados diametralmente opuestos en comparación con los mencionados aquí.

He usado 4 funciones en 4 métodos:

  • función variadic + vsnprintf+std::unique_ptr
  • función variadic + vsnprintf+std::string
  • función de plantilla variadic + std::ostringstream+ std::tuple+utility::for_each
  • fmt::formatfunción de la fmtbiblioteca

Para el backend de prueba se googletestha utilizado.

#include <string>
#include <cstdarg>
#include <cstdlib>
#include <memory>
#include <algorithm>

#include <fmt/format.h>

inline std::string string_format(size_t string_reserve, const std::string fmt_str, ...)
{
    size_t str_len = (std::max)(fmt_str.size(), string_reserve);

    // plain buffer is a bit faster here than std::string::reserve
    std::unique_ptr<char[]> formatted;

    va_list ap;
    va_start(ap, fmt_str);

    while (true) {
        formatted.reset(new char[str_len]);

        const int final_n = vsnprintf(&formatted[0], str_len, fmt_str.c_str(), ap);

        if (final_n < 0 || final_n >= int(str_len))
            str_len += (std::abs)(final_n - int(str_len) + 1);
        else
            break;
    }

    va_end(ap);

    return std::string(formatted.get());
}

inline std::string string_format2(size_t string_reserve, const std::string fmt_str, ...)
{
    size_t str_len = (std::max)(fmt_str.size(), string_reserve);
    std::string str;

    va_list ap;
    va_start(ap, fmt_str);

    while (true) {
        str.resize(str_len);

        const int final_n = vsnprintf(const_cast<char *>(str.data()), str_len, fmt_str.c_str(), ap);

        if (final_n < 0 || final_n >= int(str_len))
            str_len += (std::abs)(final_n - int(str_len) + 1);
        else {
            str.resize(final_n); // do not forget to shrink the size!
            break;
        }
    }

    va_end(ap);

    return str;
}

template <typename... Args>
inline std::string string_format3(size_t string_reserve, Args... args)
{
    std::ostringstream ss;
    if (string_reserve) {
        ss.rdbuf()->str().reserve(string_reserve);
    }
    std::tuple<Args...> t{ args... };
    utility::for_each(t, [&ss](auto & v)
    {
        ss << v;
    });
    return ss.str();
}

La for_eachimplementación se toma desde aquí: iterar sobre tupla

#include <type_traits>
#include <tuple>

namespace utility {

    template <std::size_t I = 0, typename FuncT, typename... Tp>
    inline typename std::enable_if<I == sizeof...(Tp), void>::type
        for_each(std::tuple<Tp...> &, const FuncT &)
    {
    }

    template<std::size_t I = 0, typename FuncT, typename... Tp>
    inline typename std::enable_if<I < sizeof...(Tp), void>::type
        for_each(std::tuple<Tp...> & t, const FuncT & f)
    {
        f(std::get<I>(t));
        for_each<I + 1, FuncT, Tp...>(t, f);
    }

}

Los exámenes:

TEST(ExternalFuncs, test_string_format_on_unique_ptr_0)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = string_format(0, "%s+%u\n", "test test test", 12345);
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_unique_ptr_256)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = string_format(256, "%s+%u\n", "test test test", 12345);
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_std_string_0)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = string_format2(0, "%s+%u\n", "test test test", 12345);
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_std_string_256)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = string_format2(256, "%s+%u\n", "test test test", 12345);
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_string_stream_on_variadic_tuple_0)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = string_format3(0, "test test test", "+", 12345, "\n");
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_string_stream_on_variadic_tuple_256)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = string_format3(256, "test test test", "+", 12345, "\n");
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_string_stream_inline_0)
{
    for (size_t i = 0; i < 1000000; i++) {
        std::ostringstream ss;
        ss << "test test test" << "+" << 12345 << "\n";
        const std::string v = ss.str();
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_string_stream_inline_256)
{
    for (size_t i = 0; i < 1000000; i++) {
        std::ostringstream ss;
        ss.rdbuf()->str().reserve(256);
        ss << "test test test" << "+" << 12345 << "\n";
        const std::string v = ss.str();
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_fmt_format_positional)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = fmt::format("{0:s}+{1:d}\n", "test test test", 12345);
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_fmt_format_named)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = fmt::format("{first:s}+{second:d}\n", fmt::arg("first", "test test test"), fmt::arg("second", 12345));
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

El UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR.

unsued.hpp :

#define UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(var)   ::utility::unused_param(&var)

namespace utility {

    extern const volatile void * volatile g_unused_param_storage_ptr;

    extern void
#ifdef __GNUC__
    __attribute__((optimize("O0")))
#endif
        unused_param(const volatile void * p);

}

unused.cpp :

namespace utility {

    const volatile void * volatile g_unused_param_storage_ptr = nullptr;

    void
#ifdef __GNUC__
    __attribute__((optimize("O0")))
#endif
        unused_param(const volatile void * p)
    {
        g_unused_param_storage_ptr = p;
    }

}

RESULTADOS :

[ RUN      ] ExternalFuncs.test_string_format_on_unique_ptr_0
[       OK ] ExternalFuncs.test_string_format_on_unique_ptr_0 (556 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_unique_ptr_256
[       OK ] ExternalFuncs.test_string_format_on_unique_ptr_256 (331 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_std_string_0
[       OK ] ExternalFuncs.test_string_format_on_std_string_0 (457 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_std_string_256
[       OK ] ExternalFuncs.test_string_format_on_std_string_256 (279 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_string_stream_on_variadic_tuple_0
[       OK ] ExternalFuncs.test_string_format_on_string_stream_on_variadic_tuple_0 (1214 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_string_stream_on_variadic_tuple_256
[       OK ] ExternalFuncs.test_string_format_on_string_stream_on_variadic_tuple_256 (1325 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_string_stream_inline_0
[       OK ] ExternalFuncs.test_string_format_on_string_stream_inline_0 (1208 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_string_stream_inline_256
[       OK ] ExternalFuncs.test_string_format_on_string_stream_inline_256 (1302 ms)
[ RUN      ] ExternalFuncs.test_fmt_format_positional
[       OK ] ExternalFuncs.test_fmt_format_positional (288 ms)
[ RUN      ] ExternalFuncs.test_fmt_format_named
[       OK ] ExternalFuncs.test_fmt_format_named (392 ms)

Como puede ver, la implementación a través de vsnprintf+ std::stringes igual a fmt::format, pero más rápida que a través de vsnprintf+ std::unique_ptr, que es más rápida que a través de std::ostringstream.

Las pruebas compiladas Visual Studio 2015 Update 3y ejecutadas enWindows 7 x64 / Intel Core i7-4820K CPU @ 3.70GHz / 16GB .

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.