std :: formattazione di stringhe come sprintf


454

Devo formato std::stringcon sprintfe inviare in flusso di file. Come posso fare questo?


6
lunga storia breve uso boost::format(come la soluzione di kennytm usa qui ). boost::formatsupporta già anche gli operatori di stream C ++! Esempio: cout << format("helloworld. a=%s, b=%s, c=%s") % 123 % 123.123 % "this is a test" << endl;. boost::formatha il minor numero di righe di codice ... è peer-reviewed e si integra perfettamente con i flussi C ++.
Trevor Boyd Smith,

@Ockonal - Per il bene della comunità (non me ne può fregare di meno del mio rappresentante) ti suggerisco di cambiare la tua selezione. Quello attualmente selezionato, nel primo frammento, presenta un bug in attesa che si verifichi nel suo uso di una lunghezza massima arbitraria. Il secondo frammento ignora completamente il desiderio dichiarato di utilizzare Vargs come sprintf. Ti suggerisco di selezionare SOLO la risposta che è pulita, sicura, si basa solo su standard C ++, testata e ben commentata. Che sia mio non è rilevante. È oggettivamente vero. Vedi stackoverflow.com/questions/2342162/… .
Douglas Daseeco,

@TrevorBoydSmith a è std::formatstato aggiunto a C ++ 20 BTW: stackoverflow.com/a/57286312/895245 Fantastico!
Ciro Santilli 31 冠状 病 六四 事件 法轮功

1
@CiroSantilli ho letto un articolo su C++20ieri e ho visto che è stato C++20copiato boost(per la milionesima volta) aggiungendo std::formatle C++20specifiche! Sono stato molto molto felice! Quasi tutti i file C ++ che ho scritto negli ultimi 9 anni sono stati utilizzati boost::format. l'aggiunta dell'output ufficiale in stile printf ai flussi in C ++ farà molta strada all'IMO per tutto il C ++.
Trevor Boyd Smith,

Risposte:


333

Non puoi farlo direttamente, perché non hai accesso in scrittura al buffer sottostante (fino a C ++ 11; vedi il commento di Dietrich Epp ). Dovrai prima farlo in una stringa c, quindi copiarlo in una stringa std :: string:

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

Ma non sono sicuro del perché non dovresti usare solo un flusso di stringhe? Suppongo che tu abbia motivi specifici per non solo fare questo:

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

17
Il biscotto magico char buf[100];rende questa soluzione non molto robusta. Ma l'idea essenziale è lì.
John Dibling,

18
John, i flussi non sono lenti. L'unico motivo per cui i flussi sembrano lenti è che per impostazione predefinita gli iostreams si sincronizzano con l'output C FILE in modo che cout e printfs intermittenti vengano emessi correttamente. La disabilitazione di questo collegamento (con una chiamata a cout.sync_with_stdio (false)) fa sì che i flussi di c ++ superino lo stdio, almeno a partire da MSVC10.
Jimbo,

72
Il motivo per utilizzare i formati è quello di consentire a un localizzatore di ricostruire la struttura della frase per le lingue straniere, anziché codificare a fondo la grammatica della frase.
Martijn Courteaux,

216
Per qualche motivo, altri linguaggi usano una sintassi simile a printf: Java, Python (la nuova sintassi è ancora più vicina a printf che ai flussi). Solo il C ++ infligge questo abominevole prolisso a esseri umani innocenti.
quant_dev,

9
Ancora meglio, usa asprintf, che alloca una nuova stringa con spazio sufficiente per contenere il risultato. Quindi copiarlo su un std::stringse ti piace, e ricorda freel'originale. Inoltre, è possibile inserirlo in una macro in modo che qualsiasi buon compilatore ti aiuti a convalidare il formato per te - non vuoi mettere un punto doubledove %sè previsto a
Aaron McDaid

286

Il moderno C ++ lo rende super semplice.

C ++ 20

C ++ 20 introduce std::format, che ti permette di fare esattamente questo. Utilizza campi di sostituzione simili a quelli in Python :

#include <iostream>
#include <format>

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

Controlla la documentazione completa ! È un enorme miglioramento della qualità della vita.


C ++ 11

Con C ++ 11 s std::snprintf, questo è già diventato un compito abbastanza facile e sicuro.

#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
}

Lo snippet di codice sopra riportato è concesso in licenza in CC0 1.0 .

Spiegazione riga per riga:

Obiettivo: scrivere in achar*utilizzando std::snprintfe quindi convertirlo in astd::string.

Innanzitutto, determiniamo la lunghezza desiderata dell'array char usando una condizione speciale in snprintf. Da cppreference.com :

Valore di ritorno

[...] Se la stringa risultante viene troncata a causa del limite buf_size, la funzione restituisce il numero totale di caratteri (escluso il byte null terminante) che sarebbero stati scritti se il limite non fosse stato imposto.

Ciò significa che la dimensione desiderata è il numero di caratteri più uno , in modo che il terminatore null siederà dopo tutti gli altri caratteri e che possa essere nuovamente tagliato dal costruttore di stringhe. Questo problema è stato spiegato da @ alexk7 nei commenti.

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

snprintfrestituirà un numero negativo se si è verificato un errore, quindi controlliamo se la formattazione ha funzionato come desiderato. Non farlo potrebbe portare a errori silenziosi o all'allocazione di un enorme buffer, come sottolineato da @ead nei commenti.

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

Successivamente, assegniamo un nuovo array di caratteri e lo assegniamo a std::unique_ptr. Questo è generalmente consigliato, in quanto non sarà necessario manualmente di deletenuovo.

Si noti che questo non è un modo sicuro per allocare un unique_ptrcon tipi definiti dall'utente in quanto non è possibile deallocare la memoria se il costruttore genera un'eccezione!

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

Dopodiché, possiamo ovviamente usare solo snprintfper l'uso previsto e scrivere la stringa formattata su char[].

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

Infine, ne creiamo e ne restituiamo uno nuovo std::string, assicurandoci di omettere il null-terminator alla fine.

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

Puoi vedere un esempio in azione qui .


Se vuoi usare anche std::stringnella lista degli argomenti, dai un'occhiata a questo riassunto .


Ulteriori informazioni per gli utenti di Visual Studio :

Come spiegato in questa risposta , Microsoft è stata rinominata std::snprintfin _snprintf(sì, senza std::). MS lo imposta ulteriormente come obsoleto e consiglia di utilizzare _snprintf_sinvece, tuttavia _snprintf_snon accetterà che il buffer sia zero o più piccolo dell'output formattato e non calcolerà la lunghezza degli output se ciò si verifica. Quindi, al fine di eliminare gli avvisi di deprecazione durante la compilazione, è possibile inserire la seguente riga nella parte superiore del file che contiene l'uso di _snprintf:

#pragma warning(disable : 4996)

Pensieri finali

Molte risposte a questa domanda sono state scritte prima del tempo di C ++ 11 e usano lunghezze di buffer fisse o Varg. A meno che tu non sia bloccato con le vecchie versioni di C ++, non consiglierei di usare quelle soluzioni. Idealmente, vai in C ++ 20.

Poiché la soluzione C ++ 11 in questa risposta utilizza modelli, può generare un bel po 'di codice se viene usata molto. Tuttavia, a meno che non si stia sviluppando per un ambiente con uno spazio molto limitato per i file binari, questo non sarà un problema ed è comunque un grande miglioramento rispetto alle altre soluzioni in termini di chiarezza e sicurezza.

Se l'efficienza dello spazio è estremamente importante, queste due soluzioni con vargs e vsnprintf possono essere utili. NON UTILIZZARE alcuna soluzione con buffer fissi, che richiede solo problemi.


2
Si prega di sottolineare nella risposta per gli utenti di Visual Studio che la versione di VS deve essere almeno del 2013. Da questo articolo si può vedere che funziona solo con la versione VS2013: se il buffer è un puntatore null e il conteggio è zero, viene restituito len come il conteggio dei caratteri richiesti per formattare l'output, escluso il null finale. Per effettuare una chiamata corretta con lo stesso argomento e parametri locali, allocare un buffer contenente almeno len + 1 caratteri.
cha

3
@moooeeeep Motivi multipli. In primo luogo, l'obiettivo qui è quello di restituire uno std :: string, non una c-string, quindi probabilmente intendevi return string(&buf[0], size);o qualcosa di simile. In secondo luogo, se si dovesse restituire una stringa C del genere, ciò comporterebbe un comportamento indefinito perché il vettore che contiene i valori indicati verrà invalidato al ritorno. In terzo luogo, quando ho iniziato a studiare il C ++, lo standard non definiva l'ordine in cui gli elementi dovevano essere archiviati all'interno di un std::vector, quindi accedere al suo archivio tramite un puntatore era un comportamento indefinito. Ora funzionerebbe, ma non vedo alcun beneficio nel farlo in quel modo.
iFreilicht,

2
@iFreilicht Una nuova std::stringverrà costruita dal vettore implicitamente convertito ( inizializzazione della copia ), che viene quindi restituito come copia, come suggerisce la firma della funzione. Inoltre, gli elementi di una std::vectorsono, e sono sempre stati pensati per essere, memorizzati contigui . Ma prendo in considerazione che potrebbe non esserci alcun beneficio nel farlo.
Moooeeeep,

4
Mi piace molto questa soluzione, tuttavia penso che la linea return string(buf.get(), buf.get() + size);dovrebbe essere return string(buf.get(), buf.get() + size - 1);altrimenti si ottiene una stringa con un carattere null alla fine. Ho trovato questo il caso su gcc 4.9.
Phil Williams,

3
Il passaggio di uno std :: string a% s causa un errore di compilazione ( errore: impossibile passare l'oggetto di tipo non banale 'std :: __ cxx11 :: basic_string <char>' attraverso la funzione variadic; la chiamata verrà interrotta in fase di esecuzione [-Wnon-pod -varargs] ) in clang 3.9.1, ma in CL 19 si compila bene e si blocca invece in fase di esecuzione. Qualche flag di avviso che posso attivare per avere questo cought anche in fase di compilazione in cl?
Zitrax,

241

Soluzione C ++ 11 che utilizza 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 approccio più sicuro ed efficiente (l'ho testato ed è più veloce):

#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());
}

Il fmt_strvalore viene passato per valore per essere conforme ai requisiti di va_start.

NOTA: la versione "più sicura" e "più veloce" non funziona su alcuni sistemi. Quindi entrambi sono ancora elencati. Inoltre, "più veloce" dipende interamente dalla corretta fase di preallocazione, altrimenti la strcpyrende più lenta.


3
lento. perché aumentare le dimensioni di 1? E quando questa funzione restituisce -1?
0xDEAD BEEF,

27
Stai sovrascrivendo str.c_str ()? Non è pericoloso?
quantum

8
va_start con un argomento di riferimento presenta problemi su MSVC. Non funziona in modo silenzioso e restituisce i puntatori alla memoria casuale. Per ovviare a questo problema, utilizzare std :: string fmt invece di std :: string & fmt o scrivere un oggetto wrapper.
Steve Hanov,

6
I + 1 perché so che probabilmente funzionerà in base al modo in cui viene implementata la maggior parte delle stringhe std :: string, tuttavia c_str non è realmente destinato a essere un posto per modificare la stringa sottostante. Dovrebbe essere di sola lettura.
Doug T.,

6
E per ottenere in anticipo la lunghezza della stringa risultante, vedere: stackoverflow.com/a/7825892/908336 Non vedo il punto di aumentare sizein ogni iterazione, quando è possibile ottenerla al primo richiamo di vsnprintf().
Massood Khaari,

107

boost::format() fornisce le funzionalità desiderate:

A partire dalla sinossi delle librerie di formato Boost:

Un oggetto formato è costruito da una stringa di formato e viene quindi fornito argomenti tramite ripetute chiamate all'operatore%. Ciascuno di questi argomenti viene quindi convertito in stringhe, che vengono a loro volta combinate in una stringa, in base alla stringa di 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"

5
puoi anche eliminare le librerie di cui hai bisogno. Utilizzando uno strumento fornito.
Hassan Syed,

7
Il formato boost non è solo grande, ma anche molto lento. Vedi zverovich.net/2013/09/07/… e boost.org/doc/libs/1_52_0/libs/spirit/doc/html/spirit/karma/…
vitaut

14
Includere boost ovunque nel progetto aumenta immediatamente i tempi di compilazione in modo significativo. Per progetti di grandi dimensioni, molto probabilmente non importa. Per i piccoli progetti, la spinta è un freno.
quant_dev

2
@vitaut Mentre è terribilmente consumo di risorse rispetto alle alternative. Con quale frequenza formatti le stringhe? Considerando che richiede solo pochi micro secondi e la maggior parte dei progetti probabilmente lo usano solo poche decine di volte, non è evidente in un progetto che non si concentra pesantemente sulla formattazione delle stringhe, giusto?
AturSams,

2
Sfortunatamente, boost :: format non funziona allo stesso modo: non accetta var_args. Ad alcune persone piace avere tutto il codice correlato a un singolo programma con lo stesso aspetto / usando gli stessi modi di dire.
xor007,

88

C ++ 20 includerà std::formatche assomiglia sprintfin termini di API ma è completamente sicuro per i tipi, funziona con tipi definiti dall'utente e utilizza una sintassi di stringhe di formato simile a Python. Ecco come sarai in grado di formattarlo std::stringe scriverlo su uno stream:

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());

In alternativa, è possibile utilizzare la libreria {fmt} per formattare una stringa e scriverla in stdoutun flusso di file in una volta sola:

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

Per quanto riguarda sprintfo la maggior parte delle altre risposte qui, sfortunatamente usano varargs e sono intrinsecamente non sicuri a meno che tu non usi qualcosa come l' formatattributo GCC che funziona solo con stringhe di formato letterale. Puoi vedere perché queste funzioni non sono sicure nell'esempio seguente:

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

dov'è string_formatun'implementazione dalla risposta di Erik Aronesty. Questo codice viene compilato, ma molto probabilmente si arresterà in modo anomalo quando si tenta di eseguirlo:

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

Disclaimer : sono l'autore di {fmt} e C ++ 20 std::format.


IMHO ti manca l'inclusione error: 'fmt' has not been declared
Sérgio,

Questo è solo uno snippet, non un codice completo. Ovviamente devi includere <fmt / format.h> e inserire il codice in una funzione.
vitaut

per me non è così ovvio, IMHO dovresti includerlo nello snippet, grazie per il feedback
Sérgio

1
Un'implementazione fmtsimile è stata aggiunta a C ++ 20! stackoverflow.com/a/57286312/895245 fmt attualmente richiede supporto per questo. Ottimo lavoro!
Ciro Santilli 31 冠状 病 六四 事件 法轮功

2
@vitaut Grazie per il tuo lavoro su questo!
Curt Nichols,


15

Ho scritto il mio usando vsnprintf in modo che ritorni stringa invece di dover creare il mio buffer.

#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;
}

Quindi puoi usarlo come

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

Questo fa una copia extra completa dei dati, è possibile utilizzarlo vsnprintfdirettamente nella stringa.
Mooing Duck

1
Utilizzare il codice in stackoverflow.com/a/7825892/908336 per ottenere in anticipo la lunghezza della stringa risultante. E puoi usare i puntatori intelligenti per un codice sicuro per le eccezioni:std::unique_ptr<char[]> buffer (new char[size]);
Massood Khaari,

Non sono sicuro che ciò sia corretto nel caso di fallback; Penso che sia necessario fare un va_copy di vl per il secondo vsnprintf () per vedere correttamente gli argomenti. Per un esempio, vedere: github.com/haberman/upb/blob/…
Josh Haberman,

15

Per formattare std::stringin modo "sprintf", chiamare snprintf(argomenti nullptre 0) per ottenere la lunghezza del buffer necessaria. Scrivi la tua funzione usando il modello variadic C ++ 11 in questo modo:

#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;
}

Compilare con il supporto C ++ 11, ad esempio in GCC: g++ -std=c++11

Uso:

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

std :: snprintf non è disponibile in VC ++ 12 (Visual Studio 2013). Sostituiscilo invece con _snprintf.
Shital Shah,

perché non usi char buf[length + 1];invece di char* buf = new char[length + 1];?
Behrouz.M

La differenza tra utilizzo char[]e char*con nuovo, è che nel primo caso buf sarebbe allocato in pila. Va bene per i buffer di piccole dimensioni, ma poiché non possiamo garantire la dimensione della stringa risultante, è leggermente migliore da usare new. Ad esempio sulla mia macchina string_sprintf("value: %020000000d",5), stampa un numero esagerato di zeri iniziali prima del numero 5, core dump quando si utilizza l'array in pila, ma funziona bene quando si utilizza l'array allocato in modo dinamiconew char[length + 1]
user2622016

idea molto intelligente per ottenere l'effettiva dimensione di buff necessaria per l'output formattato
Chris

1
@ user2622016: Grazie per la soluzione! Si noti che std::move è superfluo .
Mihai Todor,

14

[modifica: 20/05/25] meglio ancora ...:
nell'intestazione:

// `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 funzione PRINTSTRING(r)è quella di soddisfare la GUI o il terminale o qualsiasi uscita speciale che necessita utilizzando #ifdef _some_flag_, il valore predefinito è:

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

[modifica '17 / 8/31] Aggiunta di una versione variadica del modello '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;
}

che è effettivamente una versione delimitata da virgole (invece) degli <<operatori a volte ostacolanti , utilizzata in questo modo:

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


[modifica] Adattato per utilizzare la tecnica nella risposta di Erik Aronesty (sopra):

#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;
}

[risposta precedente]
Una risposta molto tardiva, ma per coloro a cui, come me, piace il modo 'sprintf': ho scritto e sto usando le seguenti funzioni. Se ti piace, puoi espandere le% -opzioni per adattarle più da vicino a quelle dello sprintf; quelli presenti al momento sono sufficienti per le mie esigenze. Usa stringf () e stringfappend () come faresti con sprintf. Ricorda solo che i parametri per ... devono essere tipi 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: modificato il parametro della funzione secondo il commento di Dan alla risposta di Aronesty. Uso solo Linux / gcc, e fmtcome riferimento funziona benissimo. (Ma suppongo che le persone vorranno giocare con i giocattoli, quindi ...) Se ci sono altri presunti "bug", potresti per favore elaborare?
slashmais,

Ho frainteso il modo in cui ha funzionato parte del suo codice e ho pensato che stesse facendo molti ridimensionamenti. Il riesame mostra che mi sbagliavo. Il tuo codice è corretto
Mooing Duck

Costruire fuori dalla risposta di Erik Aronesty è un'aringa rossa. Il suo primo esempio di codice non è sicuro e il secondo è inefficiente e goffo. L'implementazione pulita è chiaramente indicata dal fatto che, se buf_siz di una qualsiasi delle funzioni della famiglia vprintf è zero, nulla viene scritto e il buffer può essere un puntatore nullo, tuttavia il valore restituito (numero di byte che verrebbero scritti senza includere il terminatore null) viene comunque calcolato e restituito. Una risposta di qualità di produzione è qui: stackoverflow.com/questions/2342162/...
Douglas Daseeco

10

Ecco come fa Google: StringPrintf(Licenza BSD)
e Facebook lo fa in modo abbastanza simile: StringPrintf(Licenza Apache)
Entrambi forniscono anche un comodo StringAppendF.


10

I miei due centesimi su questa domanda molto popolare.

Per citare la manpage di printffunzioni simili :

In caso di restituzione corretta, queste funzioni restituiscono il numero di caratteri stampati (escluso il byte null utilizzato per terminare l'output in stringhe).

Le funzioni snprintf () e vsnprintf () non scrivono più di byte di dimensione (incluso il byte null di terminazione ('\ 0')). Se l'output è stato troncato a causa di questo limite, il valore restituito è il numero di caratteri (escluso il byte null terminante) che sarebbero stati scritti nella stringa finale se fosse stato disponibile spazio sufficiente. Pertanto, un valore di ritorno di dimensione o superiore indica che l'output è stato troncato.

In altre parole, un'implementazione sana di C ++ 11 dovrebbe essere la seguente:

#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);
}

Funziona abbastanza bene :)

I modelli variabili sono supportati solo in C ++ 11. La risposta di pixelpoint mostra una tecnica simile usando stili di programmazione più vecchi.

È strano che C ++ non abbia una cosa del genere. Di recente hanno aggiunto to_string(), che secondo me è un grande passo avanti. Mi chiedo se aggiungeranno un .formatoperatore alla std::stringfine ...

modificare

Come ha sottolineato alexk7, A +1è necessario sul valore restituito di std::snprintf, poiché è necessario disporre di spazio per il \0byte. Intuitivamente, sulla maggior parte delle architetture mancanti +1, il requirednumero intero verrà sovrascritto parzialmente con a 0. Ciò accadrà dopo la valutazione requiredcome parametro effettivo per std::snprintf, quindi l'effetto non dovrebbe essere visibile.

Questo problema potrebbe tuttavia cambiare, ad esempio con l'ottimizzazione del compilatore: cosa succede se il compilatore decide di utilizzare un registro per la requiredvariabile? Questo è il tipo di errori che a volte causano problemi di sicurezza.


1
snprintf aggiunge sempre un byte null che termina ma restituisce il numero di caratteri senza di esso. Questo codice non salta sempre l'ultimo carattere?
alexk7,

@ alexk7, bella cattura! Sto aggiornando la risposta. Il codice non salta l'ultimo carattere, ma scrive oltre la fine del bytesbuffer, probabilmente sopra l' requiredintero (che fortunatamente a quel punto è già valutato).
Dacav,

1
Solo un piccolo suggerimento: con una dimensione del buffer pari a 0, puoi passare a nullptrcome argomento buffer, eliminando la char b;riga nel codice. ( Fonte )
iFreilicht,

@iFreilicht, riparato. Anche +1
Dacav il

2
L'uso di "char byte [richiesto]" verrà allocato in pila anziché in heap, può essere pericoloso su stringhe di grande formato. Prendi invece in considerazione l'uso di un nuovo. 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;
}

Utilizzo di C99 snprintf e C ++ 11


9

Testato, risposta alla qualità della produzione

Questa risposta tratta il caso generale con tecniche conformi agli standard. Lo stesso approccio è fornito come esempio su CppReference.com nella parte inferiore della loro pagina. A differenza del loro esempio, questo codice soddisfa i requisiti della domanda ed è testato sul campo in applicazioni robotiche e satellitari. Ha anche migliorato i commenti. La qualità del design è discussa più avanti.

#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; }

Efficienza lineare prevedibile

Due passaggi sono necessari per una funzione riutilizzabile sicura, affidabile e prevedibile secondo le specifiche della domanda. Le presunzioni sulla distribuzione delle dimensioni dei Varg in una funzione riutilizzabile sono cattivi stili di programmazione e dovrebbero essere evitate. In questo caso, rappresentazioni di lunghezza variabile arbitrariamente grandi di Varg sono un fattore chiave nella scelta dell'algoritmo.

Riprovare in caso di overflow è esponenzialmente inefficiente, che è un altro motivo discusso quando il comitato per gli standard C ++ 11 ha discusso la proposta di cui sopra per fornire un ciclo a vuoto quando il buffer di scrittura è nullo.

Nell'implementazione pronta per la produzione di cui sopra, la prima serie è una serie così secca per determinare la dimensione di allocazione. Non si verifica alcuna allocazione. L'analisi delle direttive printf e la lettura dei Varg sono state rese estremamente efficienti nel corso di decenni. Il codice riutilizzabile dovrebbe essere prevedibile, anche se una piccola inefficienza per casi insignificanti deve essere sacrificata.

Sicurezza e affidabilità

Andrew Koenig ha detto a un piccolo gruppo di noi dopo la sua conferenza a un evento di Cambridge: "Le funzioni dell'utente non dovrebbero fare affidamento sullo sfruttamento di un fallimento per funzionalità non eccezionale". Come al solito, la sua saggezza è stata dimostrata vera nel disco da allora. Problemi di bug di sicurezza fissi e chiusi spesso indicano riprovare hack nella descrizione del buco sfruttato prima della correzione.

Questo è menzionato nella proposta di revisione degli standard formali per la funzione null buffer in Alternativa a sprintf, Proposta di revisione C9X , Documento ISO IEC WG14 N645 / X3J11 96-008 . Una stringa arbitrariamente lunga inserita per direttiva di stampa, "% s", nei limiti della disponibilità della memoria dinamica, non fa eccezione e non deve essere sfruttata per produrre "Funzionalità non eccezionale".

Considera la proposta a fianco del codice di esempio fornito nella parte inferiore della pagina C ++ Reference.org collegata al primo paragrafo di questa risposta.

Inoltre, il test dei casi di fallimento raramente è altrettanto solido dei casi di successo.

portabilità

Tutti i principali fornitori di sistemi operativi forniscono compilatori che supportano completamente std :: vsnprintf come parte degli standard c ++ 11. Gli host che eseguono prodotti di fornitori che non mantengono più le distribuzioni dovrebbero essere forniti con g ++ o clang ++ per molte ragioni.

Stack Stack

L'uso dello stack nella prima chiamata a std :: vsnprintf sarà inferiore o uguale a quello della seconda e verrà liberato prima dell'inizio della seconda chiamata. Se la prima chiamata supera la disponibilità dello stack, anche std :: fprintf fallirebbe.


Breve e robusto. Potrebbe non funzionare su HP-UX, IRIX, Tru64 che hanno vsnprintf-s non conforme. EDIT: inoltre, considerando come i due passaggi potrebbero influire sulle prestazioni, esp. per la formattazione più comune di stringhe piccole, hai considerato un'ipotesi per il passaggio iniziale, che potrebbe essere sufficientemente grande?
Ingegnere il

FWIW, la supposizione a cui mi riferivo utilizza un buffer allocato nello stack in cui si verifica la prima esecuzione. Se si adatta, consente di risparmiare il costo di una seconda corsa e l'allocazione dinamica che si verifica lì. Presumibilmente, le stringhe di piccole dimensioni vengono utilizzate più frequentemente rispetto alle stringhe di grandi dimensioni. Nel mio grezzo benchmark quella strategia (quasi) dimezza il tempo di esecuzione per stringhe piccole ed è entro poche percentuali (forse overhead fisso?) Della strategia sopra. Potresti approfondire il progetto C ++ 11 che impiega una corsa a secco, ecc.? Mi piacerebbe leggerlo.
Ingegnere il

@Engineerist, le tue domande sono state poste nel corpo della risposta, sopra e sotto il codice. Gli argomenti secondari possono essere resi più facili da leggere in questo modo.
Douglas Daseeco,

6

C ++ 20 std::format

È arrivato! La funzione è descritta su: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0645r9.html e utilizza una .format()sintassi simile a Python .

Mi aspetto che l'utilizzo sarà simile a:

#include <format>
#include <string>

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

Ci proverò quando il supporto arriva a GCC, GCC 9.1.0 con g++-9 -std=c++2aancora non lo supporta.

L'API aggiungerà una nuova std::formatintestazione:

L'API di formattazione proposta è definita nella nuova intestazione <format>e non dovrebbe avere alcun impatto sul codice esistente.

La fmtlibreria esistente afferma di implementarla se è necessario il polyfill: https://github.com/fmtlib/fmt

Implementazione di C ++ 20 std::format.

ed è stato precedentemente menzionato in: std :: string formatting come sprintf


5

Sulla base della risposta fornita da 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();
}

Questo evita la necessità di abbandonare constil risultato di .c_str()cui era nella risposta originale.


1
Costruire fuori dalla risposta di Erik Aronesty è un'aringa rossa. Il suo primo esempio di codice non è sicuro e il secondo, con il loop inefficiente e goffo. L'implementazione pulita è chiaramente indicata dal fatto che, se buf_siz di una qualsiasi delle funzioni della famiglia vprintf è zero, nulla viene scritto e il buffer può essere un puntatore nullo, tuttavia il valore restituito (numero di byte che verrebbero scritti senza includere il terminatore null) viene comunque calcolato e restituito. Una risposta di qualità di produzione è qui: stackoverflow.com/questions/2342162/...
Douglas Daseeco

La risposta di Erik Aronesty è stata modificata da quando è stata aggiunta la mia. Volevo evidenziare l'opzione di usare il vettore <char> per archiviare le stringhe mentre venivano costruite. Uso spesso questa tecnica quando chiamo le funzioni C dal codice C ++. È interessante che la domanda abbia ora 34 risposte.
ChetS

L'esempio cppreference.com nella pagina vfprintf è stato aggiunto in seguito. Credo che la risposta migliore sia la risposta attualmente accettata, l'uso di stream di stringhe anziché una variante printf è il modo di fare C ++. Tuttavia la mia risposta ha aggiunto valore quando è stata fornita; All'epoca era progressivamente meglio di altre risposte. Ora lo standard ha string_view, pacchetti di parametri e modello Variadic, una nuova risposta potrebbe includere tali funzionalità. Per quanto riguarda la mia risposta, anche se potrebbe non essere più meritevole di ulteriori voti positivi, non merita di essere cancellato o votato verso il basso, quindi lo sto lasciando così.
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 per l'idea intelligente, ma non è molto chiaro cosa _vscprintfsia. Penso che dovresti approfondire questa risposta.
Dacav,

3

stringa non ha ciò di cui hai bisogno, ma std :: stringstream ha. Utilizzare uno stringstream per creare la stringa e quindi estrarre la stringa. Ecco un elenco completo delle cose che puoi fare. Per esempio:

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

ti darà 10 decimali di precisione quando stampi un doppio o un float.


8
che comunque non ti dà nulla vicino al controllo che printf ti dà ... ma è carino.
Erik Aronesty,

3

Puoi provare questo:

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

Se utilizzi un sistema con asprintf (3) , puoi facilmente avvolgerlo:

#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
Vorrei aggiungere questa riga come una dichiarazione prima format, in quanto dice a gcc di controllare i tipi di argomenti e dare un avvertimento decente con -Wall:std::string format(const char *fmt, ...) __attribute__ ((format (printf, 1, 2)));
Aaron McDaid

2
Ho appena aggiunto una chiamata a va_end. "se va_end non viene chiamato prima che ritorni una funzione che chiama va_start o va_copy, il comportamento non è definito." - docs
Aaron McDaid,

1
È necessario controllare il risultato di ritorno di vasprintf poiché il valore del puntatore non è definito in caso di errore. Quindi, eventualmente includere <nuovo> e aggiungere: if (size == -1) {throw std :: bad_alloc (); }
Neil McGill,

Buon punto, ho modificato la risposta di conseguenza, ho deciso di inserire un commento lì invece di fare il throw std::bad_alloc();, poiché non sto usando le eccezioni C ++ nella mia base di codice e, per le persone che lo fanno, possono facilmente aggiungerlo in base sul commento sorgente e il tuo commento qui.
Thomas Perl,

2

Questo è il codice che uso per fare questo nel mio programma ... Non è niente di speciale, ma fa il trucco ... Nota, dovrai adattare le tue dimensioni come applicabile. MAX_BUFFER per me è 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;
}

4
L'inizializzazione di textString imposta già l'intero buffer su zero. Non è necessario memset ...
EricSchaefer

Questo fa una copia extra completa dei dati, è possibile utilizzarlo vsnprintfdirettamente nella stringa.
Mooing Duck

2

Prese l'idea dalla risposta di Dacav e pixelpoint . Ho giocato un po 'e ho ottenuto questo:

#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 una pratica di programmazione sana credo che il codice dovrebbe essere sufficiente, tuttavia sono ancora aperto a alternative più sicure che sono ancora abbastanza semplici e non richiederebbero C ++ 11.


Ed ecco un'altra versione che utilizza un buffer iniziale per impedire la seconda chiamata a vsnprintf()quando il buffer iniziale è già sufficiente.

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);
}

(Si scopre che questa versione è proprio simile alla risposta di Piti Ongmongkolkul , solo che non usa newe delete[], e specifica anche una dimensione durante la creazione std::string.

L'idea qui di non usare newed delete[]è implicare l'uso dello stack sull'heap poiché non ha bisogno di chiamare le funzioni di allocazione e deallocazione, tuttavia se non usato correttamente, potrebbe essere pericoloso bufferizzare gli overflow in alcuni (forse vecchi, o forse solo vulnerabili) sistemi. Se questo è un problema, consiglio vivamente di usare newe delete[]invece. Si noti che l'unica preoccupazione qui riguarda le allocazioni come vsnprintf()è già stato chiamato con limiti, quindi specificare un limite in base alla dimensione allocata sul secondo buffer impedirebbe anche quelle.)


2

Di solito lo uso:

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;
}

Svantaggio: non tutti i sistemi supportano vasprint


vasprintf è carino, tuttavia è necessario controllare il codice di ritorno. Il buffer -1 avrà un valore indefinito. Necessità: if (size == -1) {throw std :: bad_alloc (); }
Neil McGill,

2

Sotto versione leggermente modificata di @iFreilicht risposta, aggiornato per C ++ 14 (utilizzo della make_uniquefunzione invece della dichiarazione crudo) e aggiunto il supporto per std::stringargomenti (sulla base di Kenny Kerr articolo )

#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";
}

Produzione:

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

Sentiti libero di unire questa risposta con quella originale, se lo desideri.



1

È possibile formattare l'output C ++ in cout utilizzando il file di intestazione iomanip. Assicurati di includere il file di intestazione iomanip prima di utilizzare una qualsiasi delle funzioni di supporto come setprecision, setfill ecc.

Ecco uno snippet di codice che ho usato in passato per stampare il tempo medio di attesa nel vettore, che ho "accumulato".

#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;

Ecco una breve descrizione di come possiamo formattare i flussi C ++. http://www.cprogramming.com/tutorial/iomanip.html


1

Possono esserci problemi, se il buffer non è abbastanza grande per stampare la stringa. È necessario determinare la lunghezza della stringa formattata prima di stampare un messaggio formattato al suo interno. Faccio il mio aiuto per questo (testato su Windows e Linux GCC ), e puoi provare ad 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();

Per quanto riguarda la linea vsnprintf((char *)dst.data(), dst.size() + 1, format, ap);- È sicuro supporre che il buffer della stringa abbia spazio per un carattere null terminante? Esistono implementazioni che non assegnano dimensioni + 1 caratteri. Sarebbe più sicuro da faredst.resize(length+1); vsnprintf((char *)dst.data(), dst.size(), format, ap); dst.resize(length);
drwatsoncode

Apparentemente la risposta al mio commento precedente è: No NON è sicuro supporre che ci sia un carattere nullo. In particolare per quanto riguarda la specifica C ++ 98: "L'accesso al valore in data () + size () produce un comportamento indefinito : non ci sono garanzie che un carattere null termini la sequenza di caratteri puntata dal valore restituito da questa funzione. Vedi stringa :: c_str per una funzione che fornisce tale garanzia. Un programma non deve alterare nessuno dei caratteri in questa sequenza. "Tuttavia, la specifica C ++ 11 indica che datae c_strsono sinonimi.
drwatsoncode,

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

1

Soluzione molto molto semplice.

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

1

Mi rendo conto che è stato risposto più volte, ma è più 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());
}

esempio:

#include <iostream>
#include <random>

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

Vedi anche http://rextester.com/NJB14150


1

AGGIORNAMENTO 1 : aggiunti fmt::formattest

Ho preso la mia indagine sui metodi introdotti qui e ottengo risultati diametralmente opposti rispetto a quelli qui menzionati.

Ho usato 4 funzioni su 4 metodi:

  • funzione variadica + vsnprintf+std::unique_ptr
  • funzione variadica + vsnprintf+std::string
  • funzione del modello variadico + std::ostringstream+ std::tuple+utility::for_each
  • fmt::formatfunzione dalla fmtlibreria

Per il backend di test googletestè stato utilizzato.

#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();
}

L' for_eachimplementazione è presa da qui: iterare sulla 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);
    }

}

I test:

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);
    }
}

Il 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;
    }

}

RISULTATI :

[ 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)

Come puoi vedere, l'implementazione attraverso vsnprintf+ std::stringè uguale a fmt::format, ma più veloce rispetto a vsnprintf+ std::unique_ptr, che è più veloce di attraverso std::ostringstream.

I test sono stati compilati Visual Studio 2015 Update 3ed eseguiti a Windows 7 x64 / Intel Core i7-4820K CPU @ 3.70GHz / 16GB.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.