Tengo a formato std::string
con sprintf
y enviarlo a secuencia de archivo. ¿Cómo puedo hacer esto?
fuente
Tengo a formato std::string
con sprintf
y enviarlo a secuencia de archivo. ¿Cómo puedo hacer esto?
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();
char buf[100];
hace que esta solución no sea muy robusta. Pero la idea esencial está ahí.
asprintf
, que asigna una nueva cadena con suficiente espacio para contener el resultado. Luego cópielo a un std::string
si lo desea, y recuerde free
el 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 double
donde %s
se espera que sea
C ++ moderno hace que esto sea súper simple.
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.
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 .
Objetivo: escribir en achar*
usando std::snprintf
y 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;
snprintf
devolverá 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á delete
que volver a hacerlo manualmente .
Tenga en cuenta que esta no es una forma segura de asignar un unique_ptr
tipo 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 snprintf
para 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::string
partir 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::string
en 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::snprintf
a _snprintf
(sí, sin std::
). MS lo configuró como obsoleto y recomienda usarlo _snprintf_s
en 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)
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.
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.
std::string
a 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::vector
son, y siempre fueron destinados a ser, almacenados contiguamente . Pero entiendo que tal vez no sea beneficioso hacerlo.
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.
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_str
se 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 strcpy
hace más lento.
size
en cada iteración, cuando puede obtenerlo por la primera llamada de vsnprintf()
.
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"
C ++ 20 incluirá lo std::format
que se asemeja sprintf
en 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::string
y 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 stdout
una secuencia de archivos de una vez:
fmt::print(f, "Look, a string: {}", s); // where f is a file stream
En cuanto a la sprintf
mayoría de las otras respuestas aquí, desafortunadamente usan varargs y son inherentemente inseguras a menos que use algo como el format
atributo 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_format
es 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
.
error: 'fmt' has not been declared
fmt
agregó una implementación similar a C ++ 20! stackoverflow.com/a/57286312/895245 fmt actualmente reclama soporte para ello. ¡Impresionante trabajo!
Si solo desea una sintaxis similar a printf (sin llamar a printf usted mismo), eche un vistazo a Boost Format .
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);
vsnprintf
directamente en la cadena.
std::unique_ptr<char[]> buffer (new char[size]);
Para formatear std::string
de manera 'sprintf', llame a snprintf
(argumentos nullptr
y 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);
char buf[length + 1];
lugar de char* buf = new char[length + 1];
?
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]
[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;
}
fmt
referencia funciona bien. (Pero supongo que la gente querrá jugar con juguetes, así que ...) Si hay algún otro supuesto "error", ¿podría explicarlo?
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
.
Mis dos centavos en esta pregunta muy popular.
Para citar la página de manual de printf
funciones 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 .format
operador al std::string
eventualmente ...
Como señaló alexk7, +1
se necesita A en el valor de retorno de std::snprintf
, ya que necesitamos tener espacio para el \0
byte. Intuitivamente, en la mayoría de las arquitecturas que faltan +1
, el required
entero se sobrescribirá parcialmente con a 0
. Esto sucederá después de la evaluación de required
como 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 required
variable? Este es el tipo de errores que a veces resultan en problemas de seguridad.
bytes
búfer, probablemente sobre el required
entero (que afortunadamente en ese momento ya está evaluado).
nullptr
como argumento de búfer, eliminando la char b;
línea en su código. ( Fuente )
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
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á.
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++2a
todavía no lo admite.
La API agregará un nuevo std::format
encabezado:
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 fmt
biblioteca 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
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 const
el resultado del .c_str()
cual estaba en la respuesta original.
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);
}
_vscprintf
es. Creo que deberías dar más detalles sobre esta respuesta.
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.
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 );
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;
}
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)));
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
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í.
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;
}
vsnprintf
directamente en la cadena.
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 new
y delete[]
, y también especifica un tamaño al crear std::string
.
La idea aquí de no usar new
y 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 new
y 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).
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
A continuación, la versión ligeramente modificada de la respuesta @iFreilicht, actualizada a C ++ 14 (uso de la make_unique
función en lugar de declaración en bruto) y soporte adicional para std::string
argumentos (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.
La biblioteca de Poco Foundation tiene una función de formato muy conveniente, que admite std :: string tanto en la cadena de formato como en los valores:
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
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();
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);
data
y c_str
son sinónimos.
_return.desc = (boost::format("fail to detect. cv_result = %d") % st_result).str();
Solución muy, muy simple.
std::string strBuf;
strBuf.resize(256);
int iCharsPrinted = sprintf_s((char *)strPath.c_str(), strPath.size(), ...);
strBuf.resize(iCharsPrinted);
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
ACTUALIZACIÓN 1 : fmt::format
pruebas 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:
vsnprintf
+std::unique_ptr
vsnprintf
+std::string
std::ostringstream
+ std::tuple
+utility::for_each
fmt::format
función de la fmt
bibliotecaPara el backend de prueba se googletest
ha 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_each
implementació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::string
es 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 3
y ejecutadas enWindows 7 x64 / Intel Core i7-4820K CPU @ 3.70GHz / 16GB
.
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::format
tiene la menor cantidad de líneas de código ... es revisado por pares y se integra muy bien con las secuencias de C ++.std::format
se agregó a C ++ 20 BTW: stackoverflow.com/a/57286312/895245 ¡Impresionante!C++20
ayer y vi que seC++20
copióboost
(por enésima vez) agregando elstd::format
a laC++20
especificación! Estaba muy muy feliz! Se han utilizado casi todos los archivos C ++ que he escrito en los últimos 9 añosboost::format
. agregar una salida oficial de estilo printf a las transmisiones en C ++ será de gran ayuda IMO para todo C ++.