std :: string Formatierung wie sprintf

454

Ich muss Format std::stringmit sprintfund in Datei - Stream senden. Wie kann ich das machen?

Max Frai
quelle
6
lange Geschichte kurze Verwendung boost::format(wie Kennytms Lösung hier verwendet ). boost::formatunterstützt auch bereits C ++ - Stream-Operatoren! Beispiel : cout << format("helloworld. a=%s, b=%s, c=%s") % 123 % 123.123 % "this is a test" << endl;. boost::formathat die wenigsten Codezeilen ... wird von Experten begutachtet und lässt sich gut in C ++ - Streams integrieren.
Trevor Boyd Smith
@Ockonal - Aus Gründen der Community (ich könnte mich nicht weniger um meinen Repräsentanten kümmern) schlage ich vor, dass Sie Ihre Auswahl ändern. Der aktuell ausgewählte im ersten Snippet zeigt einen Fehler, der darauf wartet, bei der Verwendung einer beliebigen maximalen Länge auftreten zu können. Das zweite Snippet ignoriert Ihren erklärten Wunsch, Vargs wie Sprintf zu verwenden, vollständig. Ich schlage vor, Sie wählen hier die EINZIGE Antwort, die sauber, sicher, nur auf C ++ - Standards beruhend, getestet und gut kommentiert ist. Dass es meins ist, ist nicht relevant. Es ist objektiv wahr. Siehe stackoverflow.com/questions/2342162/… .
Douglas Daseeco
@TrevorBoydSmith a std::formatwurde zu C ++ 20 BTW hinzugefügt: stackoverflow.com/a/57286312/895245 Awesome!
Ciro Santilli 法轮功 冠状 病 六四 事件 31
1
@CiroSantilli Ich habe C++20erst gestern einen Artikel gelesen und gesehen, dass er (zum millionsten Mal) C++20kopiert wurde, boostindem ich ihn std::formatzur C++20Spezifikation hinzufügte ! Ich war sehr sehr glücklich! Fast jede C ++ - Datei, die ich in den letzten 9 Jahren geschrieben habe, wurde verwendet boost::format. Das Hinzufügen einer offiziellen Ausgabe im Printf-Stil zu Streams in C ++ wird IMO für ganz C ++ einen langen Weg gehen.
Trevor Boyd Smith

Antworten:

333

Sie können dies nicht direkt tun, da Sie keinen Schreibzugriff auf den zugrunde liegenden Puffer haben (bis C ++ 11; siehe Dietrich Epps Kommentar ). Sie müssen es zuerst in einem C-String tun und dann in einen std :: string kopieren:

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

Aber ich bin mir nicht sicher, warum Sie nicht einfach einen String-Stream verwenden würden? Ich gehe davon aus, dass Sie bestimmte Gründe haben, dies nicht einfach zu tun:

  std::ostringstream stringStream;
  stringStream << "Hello";
  std::string copyOfStr = stringStream.str();
Doug T.
quelle
17
Der magische Cookie char buf[100];macht diese Lösung nicht sehr robust. Aber die wesentliche Idee ist da.
John Dibling
18
John, Streams sind nicht langsam. Der einzige Grund, warum Streams langsam erscheinen, besteht darin, dass die iostreams standardmäßig mit der C FILE-Ausgabe synchronisiert werden, sodass vermischte cout- und printfs-Daten korrekt ausgegeben werden. Das Deaktivieren dieses Links (mit einem Aufruf von cout.sync_with_stdio (false)) führt dazu, dass die Streams von c ++ stdio zumindest ab MSVC10 übertreffen.
Jimbo
72
Der Grund für die Verwendung von Formaten besteht darin, dass ein Lokalisierer die Struktur des Satzes für Fremdsprachen neu erstellt, anstatt die Grammatik des Satzes hart zu codieren.
Martijn Courteaux
216
Aus irgendeinem Grund verwenden andere Sprachen eine printf-ähnliche Syntax: Java, Python (die neue Syntax ist printf immer noch näher als Streams). Nur C ++ fügt unschuldigen Menschen diesen wortreichen Gräuel zu.
quant_dev
9
Noch besser ist use asprintf, das eine neue Zeichenfolge mit genügend Speicherplatz für das Ergebnis zuweist. Kopieren Sie das dann auf ein, std::stringwenn Sie möchten, und denken Sie an freedas Original. Es ist auch möglich, dies in ein Makro einzufügen, damit jeder gute Compiler dabei hilft, das Format für Sie zu validieren - Sie möchten nicht angeben, doublewo a %serwartet wird
Aaron McDaid
286

Modernes C ++ macht dies super einfach.

C ++ 20

C ++ 20 wird eingeführt std::format, mit dem Sie genau das tun können. Es werden Ersatzfelder verwendet, die denen in Python ähneln :

#include <iostream>
#include <format>

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

Lesen Sie die vollständige Dokumentation ! Es ist eine enorme Verbesserung der Lebensqualität.


C ++ 11

Mit C ++ 11 s std::snprintf, wurde dies bereits eine ziemlich einfache und sichere Aufgabe.

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

Das obige Code-Snippet ist unter CC0 1.0 lizenziert .

Zeile für Zeile Erklärung:

Ziel: Schreibe einechar*durchVerwendung std::snprintfund dann konvertierendas zu einstd::string.

Zuerst bestimmen wir die gewünschte Länge des char-Arrays unter Verwendung einer speziellen Bedingung in snprintf. Von cppreference.com :

Rückgabewert

[...] Wenn die resultierende Zeichenfolge aufgrund des Grenzwerts für buf_size abgeschnitten wird, gibt die Funktion die Gesamtzahl der Zeichen (ohne das abschließende Nullbyte) zurück, die geschrieben worden wären, wenn der Grenzwert nicht festgelegt worden wäre.

Dies bedeutet, dass die gewünschte Größe die Anzahl der Zeichen plus eins ist , sodass der Nullterminator hinter allen anderen Zeichen steht und vom Zeichenfolgenkonstruktor wieder abgeschnitten werden kann. Dieses Problem wurde von @ alexk7 in den Kommentaren erklärt.

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

snprintfgibt eine negative Zahl zurück, wenn ein Fehler aufgetreten ist. Daher prüfen wir, ob die Formatierung wie gewünscht funktioniert hat. Wenn Sie dies nicht tun, kann dies zu stillen Fehlern oder zur Zuweisung eines großen Puffers führen, wie @ead in den Kommentaren hervorhebt.

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

Als nächstes weisen wir ein neues Zeichenarray zu und weisen es einem zu std::unique_ptr. Dies wird im Allgemeinen empfohlen, da Sie es nicht deleteerneut manuell ausführen müssen.

Beachten Sie, dass dies keine sichere Methode zum Zuweisen eines unique_ptrmit benutzerdefinierten Typen ist, da Sie den Speicher nicht freigeben können, wenn der Konstruktor eine Ausnahme auslöst!

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

Danach können wir natürlich nur snprintffür den vorgesehenen Verwendungszweck verwenden und den formatierten String in das schreiben char[].

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

Schließlich erstellen wir ein neues und geben es zurück, wobei wir std::stringsicherstellen, dass der Null-Terminator am Ende weggelassen wird.

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

Sie können ein Beispiel in Aktion sehen hier .


Wenn Sie auch std::stringin der Argumentliste verwenden möchten, werfen Sie einen Blick auf diesen Kern .


Zusätzliche Informationen für Visual Studio- Benutzer:

Wie in erklärt diese Antwort , umbenannt Microsoft std::snprintfauf _snprintf(ja, ohne std::). MS setzt es weiter als veraltet und empfiehlt, es _snprintf_sstattdessen zu verwenden , _snprintf_sakzeptiert jedoch nicht, dass der Puffer Null oder kleiner als die formatierte Ausgabe ist, und berechnet in diesem Fall nicht die Ausgabelänge. Um die Verfallswarnungen während der Kompilierung zu entfernen, können Sie die folgende Zeile oben in die Datei einfügen, die Folgendes enthält _snprintf:

#pragma warning(disable : 4996)

Abschließende Gedanken

Viele Antworten auf diese Frage wurden vor C ++ 11 geschrieben und verwenden feste Pufferlängen oder Variablen. Wenn Sie nicht mit alten Versionen von C ++ feststecken, würde ich die Verwendung dieser Lösungen nicht empfehlen. Im Idealfall gehen Sie den C ++ 20-Weg.

Da die C ++ 11-Lösung in dieser Antwort Vorlagen verwendet, kann sie viel Code generieren, wenn sie häufig verwendet wird. Sofern Sie nicht für eine Umgebung mit sehr begrenztem Speicherplatz für Binärdateien entwickeln, ist dies kein Problem und stellt in Bezug auf Klarheit und Sicherheit immer noch eine enorme Verbesserung gegenüber den anderen Lösungen dar.

Wenn die Raumeffizienz sehr wichtig ist, können diese beiden Lösungen mit vargs und vsnprintf nützlich sein. Verwenden Sie keine Lösungen mit festen Pufferlängen, da dies nur zu Problemen führt.

iFreilicht
quelle
2
Bitte betonen Sie in Ihrer Antwort für die Visual Studio-Benutzer, dass die Version von VS mindestens 2013 sein muss. Aus diesem Artikel geht hervor, dass sie nur mit der VS2013-Version funktioniert: Wenn buffer ein Nullzeiger ist und count null ist, wird len als zurückgegeben Die Anzahl der Zeichen, die zum Formatieren der Ausgabe erforderlich sind, ohne die abschließende Null. Um einen erfolgreichen Aufruf mit denselben Argument- und Gebietsschemaparametern durchzuführen, weisen Sie einen Puffer zu, der mindestens len + 1 Zeichen enthält.
Cha
3
@moooeeeep Mehrere Gründe. Erstens ist das Ziel hier, einen std :: string zurückzugeben, keinen c-string, also meinten Sie wahrscheinlich return string(&buf[0], size);oder etwas Ähnliches. Zweitens würde eine solche C-Zeichenfolge undefiniertes Verhalten verursachen, da der Vektor, der die Werte enthält, auf die Sie zeigen, bei der Rückgabe ungültig wird. Drittens, als ich anfing, C ++ zu lernen, definierte der Standard nicht, in welcher Reihenfolge Elemente in einem gespeichert werden mussten std::vector, sodass der Zugriff auf seinen Speicher über einen Zeiger ein undefiniertes Verhalten war. Jetzt würde es funktionieren, aber ich sehe keinen Vorteil darin, es so zu machen.
iFreilicht
2
Eine neue @iFreilicht std::stringwerden aus der implizit konvertiert Vektor (konstruiert werden Kopie Initialisierung ), die dann als Kopie zurückgegeben wird, da die Funktionssignatur vorschlägt. Auch die Elemente von a std::vectorwerden und sollten immer zusammenhängend gespeichert werden . Aber ich gehe davon aus, dass dies möglicherweise keinen Nutzen bringt.
Moooeeeep
4
Ich mag diese Lösung wirklich, aber ich denke, die Zeile return string(buf.get(), buf.get() + size);sollte so sein return string(buf.get(), buf.get() + size - 1);, dass Sie am Ende eine Zeichenfolge mit einem Nullzeichen erhalten. Ich fand dies bei gcc 4.9 der Fall.
Phil Williams
3
Das Übergeben eines std :: string an% s führt zu einem Kompilierungsfehler ( Fehler: Objekt des nicht trivialen Typs 'std :: __ cxx11 :: basic_string <char>' kann nicht über eine variable Funktion übergeben werden; der Aufruf wird zur Laufzeit abgebrochen [-Wnon-pod -varargs] ) in clang 3.9.1, aber in CL 19 wird es gut kompiliert und stürzt stattdessen zur Laufzeit ab. Gibt es eine Warnflagge, die ich aktivieren kann, damit diese auch zur Kompilierungszeit in cl angezeigt wird?
Zitrax
241

C ++ 11-Lösung, die vsnprintf()intern verwendet:

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

Ein sicherer und effizienter Ansatz (ich habe ihn getestet und er ist schneller):

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

Der fmt_strWert wird übergeben, um den Anforderungen von zu entsprechen va_start.

HINWEIS: Die "sicherere" und "schnellere" Version funktioniert auf einigen Systemen nicht. Daher sind beide noch aufgeführt. Außerdem hängt "schneller" vollständig davon ab, ob der Vorbelegungsschritt korrekt ist, andernfalls wird strcpyer langsamer.

Erik Aronesty
quelle
3
langsam. Warum um 1 erhöhen? Und wann gibt diese Funktion -1 zurück?
0xDEAD BEEF
27
Sie überschreiben str.c_str ()? Ist das nicht gefährlich?
Quanten
8
va_start mit einem Referenzargument hat Probleme mit MSVC. Es schlägt stillschweigend fehl und gibt Zeiger auf den zufälligen Speicher zurück. Verwenden Sie zur Umgehung dieses Problems std :: string fmt anstelle von std :: string & fmt oder schreiben Sie ein Wrapper-Objekt.
Steve Hanov
6
Ich + 1 würde, weil ich weiß, dass dies wahrscheinlich basierend auf der Implementierung der meisten std :: -Strings funktionieren wird, aber c_str ist nicht wirklich als Ort zum Ändern des zugrunde liegenden Strings gedacht. Es soll schreibgeschützt sein.
Doug T.
6
Informationen zum Abrufen der resultierenden Zeichenfolgenlänge finden Sie unter: stackoverflow.com/a/7825892/908336 Ich sehe keinen Sinn darin, sizein jeder Iteration zuzunehmen , wenn Sie sie beim ersten Aufruf von erhalten können vsnprintf().
Massood Khaari
107

boost::format() bietet die gewünschte Funktionalität:

Ab der Übersicht über die Boost-Formatbibliotheken:

Ein Formatobjekt wird aus einer Formatzeichenfolge erstellt und erhält dann durch wiederholte Aufrufe des Operators% Argumente. Jedes dieser Argumente wird dann in Zeichenfolgen konvertiert, die wiederum entsprechend der Formatzeichenfolge zu einer Zeichenfolge kombiniert werden.

#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"
kennytm
quelle
5
Sie können die benötigten Bibliotheken auch aus Boost heraus beschneiden. Verwenden eines mitgelieferten Werkzeugs.
Hassan Syed
7
Das Boost-Format ist nicht nur groß, sondern auch sehr langsam. Siehe zverovich.net/2013/09/07/… und boost.org/doc/libs/1_52_0/libs/spirit/doc/html/spirit/karma/…
vitaut
14
Durch die sofortige Aufnahme von Boost in Ihr Projekt werden die Kompilierungszeiten sofort erheblich verlängert. Bei großen Projekten spielt es höchstwahrscheinlich keine Rolle. Bei kleinen Projekten ist Boost ein Problem.
quant_dev
2
@vitaut Während es im Vergleich zu den Alternativen furchtbar ressourcenintensiv ist. Wie oft formatieren Sie Zeichenfolgen? Wenn man bedenkt, dass es nur ein paar Mikrosekunden dauert und die meisten Projekte es wahrscheinlich nur ein paar Dutzend Mal verwenden, fällt es in einem Projekt, das sich nicht stark auf die Formatierung von Zeichenfolgen konzentriert, nicht auf, oder?
AturSams
2
Leider funktioniert das boost :: -Format nicht auf die gleiche Weise: Akzeptiert die var_args nicht. Einige Leute möchten, dass der gesamte Code, der sich auf ein einzelnes Programm bezieht, gleich aussieht / dieselben Redewendungen verwendet.
xor007
88

C ++ 20 wird Folgendes enthalten, std::formatdas sprintfin Bezug auf die API ähnelt, jedoch vollständig typsicher ist, mit benutzerdefinierten Typen arbeitet und eine Python-ähnliche Format-String-Syntax verwendet. So können Sie es formatieren std::stringund in einen Stream schreiben:

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

oder

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

Alternativ können Sie die {fmt} -Bibliothek verwenden, um eine Zeichenfolge zu formatieren und in stdouteinen Dateistream auf einmal zu schreiben :

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

Die sprintfmeisten anderen Antworten hier verwenden leider varargs und sind von Natur aus unsicher, es sei denn, Sie verwenden so etwas wie das formatAttribut von GCC , das nur mit Zeichenfolgen im Literalformat funktioniert. Im folgenden Beispiel können Sie sehen, warum diese Funktionen unsicher sind:

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

Wo string_formatist eine Implementierung aus der Antwort von Erik Aronesty. Dieser Code wird kompiliert, stürzt jedoch höchstwahrscheinlich ab, wenn Sie versuchen, ihn auszuführen:

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

Haftungsausschluss : Ich bin der Autor von {fmt} und C ++ 20 std::format.

vitaut
quelle
IMHO vermissen Sie das Include error: 'fmt' has not been declared
Sérgio
Dies ist nur ein Ausschnitt, kein vollständiger Code. Natürlich müssen Sie <fmt / format.h> einschließen und den Code in eine Funktion einfügen.
Vitaut
für mich ist nicht so offensichtlich, IMHO sollten Sie es in Snippet aufnehmen, danke für das Feedback
Sérgio
1
Eine fmtähnliche Implementierung wurde zu C ++ 20 hinzugefügt! stackoverflow.com/a/57286312/895245 fmt beansprucht derzeit Unterstützung dafür. Tolle Arbeit!
Ciro Santilli 法轮功 冠状 病 六四 事件 31
2
@vitaut Danke für deine Arbeit daran!
Curt Nichols
18

Wenn Sie nur eine printf-ähnliche Syntax wünschen (ohne printf selbst aufzurufen), schauen Sie sich das Boost-Format an .

Timo Geusch
quelle
Das Hinzufügen einer gesamten Bibliothek für eine so einfache Sache ist nicht erforderlich. Dies wurde unter stackoverflow.com/questions/19009094/… beantwortet .
Douglas Daseeco
15

Ich habe mein eigenes mit vsnprintf geschrieben, damit es einen String zurückgibt, anstatt meinen eigenen Puffer erstellen zu müssen.

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

So können Sie es wie verwenden

std::string mystr = format("%s %d %10.5f", "omg", 1, 10.5);
Piti Ongmongkolkul
quelle
Dadurch wird eine vollständige zusätzliche Kopie der Daten erstellt, die vsnprintfdirekt in die Zeichenfolge verwendet werden kann.
Mooing Duck
1
Verwenden Sie den Code in stackoverflow.com/a/7825892/908336 , um die resultierende Zeichenfolgenlänge im Voraus zu erhalten. Und Sie können intelligente Zeiger für einen std::unique_ptr<char[]> buffer (new char[size]);
ausnahmesicheren
Ich bin mir nicht sicher, ob dies im Fallback-Fall richtig ist. Ich denke, Sie müssen eine va_copy von vl für das zweite vsnprintf () durchführen, um die Argumente korrekt zu sehen. Ein Beispiel finden Sie unter: github.com/haberman/upb/blob/…
Josh Haberman
15

Um std::stringim Sprintf- Format zu formatieren , rufen Sie snprintf(Argumente nullptrund 0) auf, um die benötigte Pufferlänge zu erhalten. Schreiben Sie Ihre Funktion mit einer variablen C ++ 11-Vorlage wie folgt:

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

Kompilieren Sie mit C ++ 11-Unterstützung, zum Beispiel in GCC: g++ -std=c++11

Verwendungszweck:

  std::cout << string_sprintf("%g, %g\n", 1.23, 0.001);
user2622016
quelle
std :: snprintf ist in VC ++ 12 (Visual Studio 2013) nicht verfügbar. Ersetzen Sie es stattdessen durch _snprintf.
Shital Shah
warum benutzt du nicht char buf[length + 1];statt char* buf = new char[length + 1];?
Behrouz.M
Der Unterschied zwischen using char[]und char*with new besteht darin, dass im ersteren Fall buf auf Stack zugewiesen wird. Es ist in Ordnung für kleine Puffer, aber da wir die Größe der resultierenden Zeichenfolge nicht garantieren können, ist die Verwendung etwas besser new. string_sprintf("value: %020000000d",5)Drucken Sie beispielsweise auf meinem Computer eine unverschämte Anzahl führender Nullen vor Nummer 5, Core-Dumps, wenn Sie ein Array auf einem Stapel verwenden, aber funktionieren Sie new char[length + 1]
einwandfrei,
Sehr clevere Idee, um die tatsächliche Buff-Größe für die formatierte Ausgabe zu erhalten
Chris
1
@ user2622016: Danke für die Lösung! Bitte beachten Sie, dass dies std::move überflüssig ist .
Mihai Todor
14

[edit: 20/05/25] noch besser ...:
In der Kopfzeile:

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

Die Funktion PRINTSTRING(r)besteht darin, die Benutzeroberfläche oder das Terminal oder spezielle Ausgabeanforderungen zu berücksichtigen #ifdef _some_flag_. Die Standardeinstellung lautet:

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

[edit '17 / 8/31] Hinzufügen einer variadic templated version '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;
}

<<Dies ist effektiv eine durch Kommas getrennte Version (anstelle) der manchmal behindernden Operatoren, die wie folgt verwendet werden:

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


[Bearbeiten] Angepasst, um die Technik in Erik Aronestys Antwort (oben) zu nutzen:

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

[vorherige Antwort]
Eine sehr späte Antwort, aber für diejenigen, die wie ich den 'Sprintf'-Weg mögen: Ich habe geschrieben und benutze die folgenden Funktionen. Wenn es Ihnen gefällt, können Sie die% -Optionen erweitern, um sie besser an die Sprint-Optionen anzupassen. Die dort sind derzeit für meine Bedürfnisse ausreichend. Sie verwenden stringf () und stringfappend () wie sprintf. Denken Sie daran, dass die Parameter für ... POD-Typen sein müssen.

//=============================================================================
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;
}
Slashmais
quelle
@MooingDuck: Der Funktionsparameter wurde gemäß Dans Kommentar zu Aronestys Antwort geändert. Ich benutze nur Linux / gcc und fmtals Referenz funktioniert es gut. (Aber ich nehme an, die Leute werden mit Spielzeug spielen wollen, also ...) Wenn es andere vermeintliche 'Bugs' gibt, könnten Sie das bitte näher erläutern?
Slashmais
Ich habe falsch verstanden, wie ein Teil seines Codes funktioniert, und dachte, es würde zu vielen Größenänderungen führen. Eine erneute Untersuchung zeigt, dass ich mich geirrt habe. Ihr Code ist korrekt.
Mooing Duck
Aufbauend auf Erik Aronestys Antwort ist ein roter Hering. Sein erstes Codebeispiel ist unsicher und sein zweites ist ineffizient und ungeschickt. Die saubere Implementierung wird deutlich durch die Tatsache angezeigt, dass, wenn die buf_siz einer der vprintf-Funktionsfamilien Null ist, nichts geschrieben wird und der Puffer ein Nullzeiger sein kann, jedoch der Rückgabewert (Anzahl der zu schreibenden Bytes ohne) der Nullterminator) wird weiterhin berechnet und zurückgegeben. Eine Antwort auf die Produktionsqualität finden Sie hier: stackoverflow.com/questions/2342162/…
Douglas Daseeco
10

So macht es Google: StringPrintf(BSD-Lizenz)
und Facebook machen es auf ganz ähnliche Weise: StringPrintf(Apache-Lizenz)
Beide bieten auch eine bequeme StringAppendF.

PW.
quelle
10

Meine zwei Cent zu dieser sehr beliebten Frage.

Um die Manpage printfähnlicher Funktionen zu zitieren :

Bei erfolgreicher Rückgabe geben diese Funktionen die Anzahl der gedruckten Zeichen zurück (mit Ausnahme des Nullbytes, mit dem die Ausgabe an Zeichenfolgen beendet wird).

Die Funktionen snprintf () und vsnprintf () schreiben nicht mehr als Größenbytes (einschließlich des abschließenden Nullbytes ('\ 0')). Wenn die Ausgabe aufgrund dieser Begrenzung abgeschnitten wurde, ist der Rückgabewert die Anzahl der Zeichen (ohne das abschließende Nullbyte), die in die endgültige Zeichenfolge geschrieben worden wären, wenn genügend Speicherplatz verfügbar gewesen wäre. Ein Rückgabewert von Größe oder mehr bedeutet also, dass die Ausgabe abgeschnitten wurde.

Mit anderen Worten, eine vernünftige C ++ 11-Implementierung sollte wie folgt aussehen:

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

Es funktioniert ganz gut :)

Variadische Vorlagen werden nur in C ++ 11 unterstützt. Die Antwort von Pixelpoint zeigt eine ähnliche Technik unter Verwendung älterer Programmierstile.

Es ist seltsam, dass C ++ so etwas nicht sofort einsatzbereit hat. Sie haben kürzlich hinzugefügt to_string(), was meiner Meinung nach ein großer Schritt nach vorne ist. Ich frage mich, ob sie irgendwann einen .formatOperator zum std::string...

Bearbeiten

Wie alexk7 hervorhob, wird A +1für den Rückgabewert von benötigt std::snprintf, da wir Platz für das \0Byte benötigen . Intuitiv führt bei den meisten fehlenden Architekturen +1dazu, dass die requiredGanzzahl teilweise mit a überschrieben wird 0. Dies geschieht nach der Auswertung requiredals Istparameter für std::snprintf, daher sollte der Effekt nicht sichtbar sein.

Dieses Problem könnte sich jedoch beispielsweise bei der Compileroptimierung ändern: Was passiert, wenn der Compiler beschließt, ein Register für die requiredVariable zu verwenden? Dies ist die Art von Fehlern, die manchmal zu Sicherheitsproblemen führen.

Dacav
quelle
1
snprintf hängt immer ein abschließendes Null-Byte an, gibt jedoch die Anzahl der Zeichen ohne dieses zurück. Überspringt dieser Code nicht immer das letzte Zeichen?
Alexk7
@ alexk7, schöner Fang! Ich aktualisiere die Antwort. Der Code überspringt nicht das letzte Zeichen, sondern schreibt über das Ende des bytesPuffers hinaus, wahrscheinlich über die requiredGanzzahl (die zum Glück zu diesem Zeitpunkt bereits ausgewertet ist).
Dacav
1
Nur ein kleiner Hinweis: Mit einer Puffergröße von 0 können Sie ein nullptrals Pufferargument übergeben, wodurch die char b;Zeile in Ihrem Code entfernt wird. ( Quelle )
iFreilicht
@iFreilicht, behoben. Auch +1
Dacav
2
Die Verwendung von "char bytes [erforderlich]" wird auf dem Stapel anstelle des Heapspeichers zugewiesen. Dies kann bei großformatigen Zeichenfolgen gefährlich sein. Verwenden Sie stattdessen ein neues. 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;
}

Verwenden von C99 snprintf und C ++ 11

aaa
quelle
9

Getestet, Antwort auf die Produktionsqualität

Diese Antwort behandelt den allgemeinen Fall mit standardkonformen Techniken. Der gleiche Ansatz wird als Beispiel auf CppReference.com am Ende ihrer Seite angegeben. Im Gegensatz zu ihrem Beispiel entspricht dieser Code den Anforderungen der Frage und wird in Robotik- und Satellitenanwendungen vor Ort getestet. Es hat auch das Kommentieren verbessert. Die Designqualität wird weiter unten erläutert.

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

Vorhersagbare lineare Effizienz

Zwei Durchgänge sind für eine sichere, zuverlässige und vorhersehbare wiederverwendbare Funktion gemäß den Fragenspezifikationen erforderlich. Vermutungen über die Verteilung der Größe von Variablen in einer wiederverwendbaren Funktion sind ein schlechter Programmierstil und sollten vermieden werden. In diesem Fall sind beliebig große Darstellungen variabler Länge von Variablen ein Schlüsselfaktor bei der Wahl des Algorithmus.

Ein erneuter Versuch beim Überlauf ist exponentiell ineffizient. Dies ist ein weiterer Grund, der erörtert wurde, als das C ++ 11-Standardkomitee den obigen Vorschlag erörterte, einen Probelauf bereitzustellen, wenn der Schreibpuffer Null ist.

In der obigen produktionsfertigen Implementierung ist der erste Lauf ein solcher Trockenlauf, um die Zuordnungsgröße zu bestimmen. Es erfolgt keine Zuordnung. Das Parsen von printf-Anweisungen und das Lesen von vargs wurde über Jahrzehnte äußerst effizient gemacht. Wiederverwendbarer Code sollte vorhersehbar sein, auch wenn eine kleine Ineffizienz für triviale Fälle geopfert werden muss.

Sicherheit und Zuverlässigkeit

Andrew Koenig sagte nach seinem Vortrag auf einer Veranstaltung in Cambridge zu einer kleinen Gruppe von uns: "Benutzerfunktionen sollten sich nicht darauf verlassen, dass ein Fehler für außergewöhnliche Funktionen ausgenutzt wird." Wie üblich hat sich seine Weisheit seitdem in der Aufzeichnung als wahr erwiesen. Behobene und geschlossene Sicherheitslücken weisen häufig auf Wiederholungshacks in der Beschreibung des vor dem Fix ausgenutzten Lochs hin.

Dies wird im Überarbeitungsvorschlag für formale Standards für die Nullpufferfunktion in Alternative zu Sprintf, C9X-Überarbeitungsvorschlag , ISO IEC-Dokument WG14 N645 / X3J11 96-008 erwähnt . Eine willkürlich lange Zeichenfolge, die gemäß der Druckanweisung "% s" im Rahmen der dynamischen Speicherverfügbarkeit eingefügt wird, ist keine Ausnahme und sollte nicht zur Erzeugung von "nicht außergewöhnlichen Funktionen" verwendet werden.

Betrachten Sie den Vorschlag neben dem Beispielcode, der unten auf der Seite C ++ Reference.org angegeben ist, auf die im ersten Absatz dieser Antwort verwiesen wird.

Auch das Testen von Fehlerfällen ist selten so robust wie Erfolgsfälle.

Portabilität

Alle großen Betriebssystemanbieter bieten Compiler an, die std :: vsnprintf als Teil der c ++ 11-Standards vollständig unterstützen. Hosts, auf denen Produkte von Anbietern ausgeführt werden, die keine Distributionen mehr verwalten, sollten aus vielen Gründen mit g ++ oder clang ++ ausgestattet sein.

Stapelverwendung

Die Stapelverwendung im ersten Aufruf von std :: vsnprintf ist kleiner oder gleich der des zweiten Aufrufs und wird freigegeben, bevor der zweite Aufruf beginnt. Wenn der erste Aufruf die Stapelverfügbarkeit überschreitet, schlägt auch std :: fprintf fehl.

Douglas Daseeco
quelle
Kurz und robust. Es könnte unter HP-UX, IRIX, Tru64 fehlschlagen, die nicht konforme vsnprintf-s haben. BEARBEITEN: Auch wenn man bedenkt, wie sich zwei Durchgänge auf die Leistung auswirken könnten, insb. Haben Sie bei den meisten gängigen Formatierungen für kleine Zeichenfolgen eine Vermutung für den ersten Durchgang in Betracht gezogen, die möglicherweise ausreichend groß ist?
Ingenieur
FWIW, die Vermutung, auf die ich mich bezog, verwendet einen vom Stapel zugewiesenen Puffer, in dem der erste Lauf stattfindet. Wenn es passt, spart es die Kosten für einen zweiten Lauf und die dort auftretende dynamische Zuordnung. Vermutlich werden kleine Saiten häufiger verwendet als große. In meinem groben Benchmark halbiert diese Strategie (fast) die Laufzeit für kleine Saiten und liegt innerhalb einiger Prozent (fester Overhead vielleicht?) Der obigen Strategie. Würden Sie bitte das C ++ 11-Design erläutern, das einen Trockenlauf usw. verwendet? Ich würde gerne darüber lesen.
Ingenieur
@Engineerist, Ihre Fragen wurden im Hauptteil der Antwort über und unter dem Code beantwortet. Die Unterthemen können auf diese Weise leichter lesbar gemacht werden.
Douglas Daseeco
6

C ++ 20 std::format

Es ist angekommen! Die Funktion wird unter folgender Adresse beschrieben: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0645r9.html und verwendet eine Python-ähnliche .format()Syntax.

Ich erwarte, dass die Verwendung wie folgt sein wird:

#include <format>
#include <string>

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

Ich werde es versuchen, wenn der Support bei GCC eintrifft. GCC 9.1.0 g++-9 -std=c++2aunterstützt ihn immer noch nicht.

Die API fügt einen neuen std::formatHeader hinzu:

Die vorgeschlagene Formatierungs-API wird im neuen Header definiert <format>und sollte keine Auswirkungen auf vorhandenen Code haben.

Die vorhandene fmtBibliothek behauptet, sie zu implementieren, wenn Sie die Polyfüllung benötigen: https://github.com/fmtlib/fmt

Implementierung von C ++ 20 std::format.

und wurde bereits erwähnt unter: std :: string Formatierung wie sprintf

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
quelle
5

Basierend auf der Antwort von 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();
}

Dies vermeidet die Notwendigkeit, constvon dem Ergebnis, .c_str()das in der ursprünglichen Antwort enthalten war, abzuwerfen.

ChetS
quelle
1
Aufbauend auf Erik Aronestys Antwort ist ein roter Hering. Sein erstes Codebeispiel ist unsicher und sein zweites mit der Schleife ist ineffizient und ungeschickt. Die saubere Implementierung wird deutlich durch die Tatsache angezeigt, dass, wenn die buf_siz einer der vprintf-Funktionsfamilien Null ist, nichts geschrieben wird und der Puffer ein Nullzeiger sein kann, jedoch der Rückgabewert (Anzahl der zu schreibenden Bytes ohne) der Nullterminator) wird weiterhin berechnet und zurückgegeben. Eine Antwort auf die Produktionsqualität finden Sie hier: stackoverflow.com/questions/2342162/…
Douglas Daseeco
Die Antwort von Erik Aronesty wurde bearbeitet, seit meine hinzugefügt wurde. Ich wollte die Option hervorheben, Vektor <char> zum Speichern von Zeichenfolgen zu verwenden, während diese erstellt werden. Ich benutze diese Technik oft, wenn ich C-Funktionen aus C ++ - Code aufrufe. Es ist interessant, dass die Frage jetzt 34 Antworten hat.
ChetS
Das Beispiel cppreference.com auf der Seite vfprintf wurde später hinzugefügt. Ich glaube, die beste Antwort ist die derzeit akzeptierte Antwort. Die Verwendung von String-Streams anstelle einer printf-Variante ist die C ++ - Methode. Meine Antwort hat jedoch einen Mehrwert gebracht, als sie zur Verfügung gestellt wurde. Es war inkrementell besser als andere Antworten zu der Zeit. Jetzt hat der Standard string_view, Parameterpakete und eine Variadic-Vorlage. Eine neue Antwort könnte diese Funktionen enthalten. Was meine Antwort betrifft, obwohl sie möglicherweise keine zusätzlichen Aufwärtsstimmen mehr verdient, verdient sie es nicht, gelöscht oder herabgestimmt zu werden, also lasse ich sie so wie sie ist.
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);
}
Pixelpunkt
quelle
1
+1 für die kluge Idee, aber es ist nicht sehr klar, was _vscprintfist. Ich denke, Sie sollten diese Antwort näher erläutern.
Dacav
3

string hat nicht das, was du brauchst, aber std :: stringstream. Verwenden Sie einen Stringstream, um den String zu erstellen und den String dann zu extrahieren. Hier ist eine umfassende Liste der Dinge, die Sie tun können. Zum Beispiel:

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

Sie erhalten 10 Dezimalstellen Genauigkeit, wenn Sie ein Double oder Float drucken.

Hassan Syed
quelle
8
Das gibt dir immer noch nichts in der Nähe der Kontrolle, die printf dir gibt ... aber es ist nett.
Erik Aronesty
3

Sie könnten dies versuchen:

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 );
EddieV223
quelle
3

Wenn Sie sich auf einem System mit asprintf (3) befinden , können Sie es einfach umbrechen :

#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;
}
Thomas Perl
quelle
2
Ich würde diese Zeile vorher als Erklärung hinzufügen format, da sie gcc anweist, die Arten der Argumente zu überprüfen und mit -Wall eine anständige Warnung zu geben:std::string format(const char *fmt, ...) __attribute__ ((format (printf, 1, 2)));
Aaron McDaid
2
Ich habe gerade einen Anruf hinzugefügt va_end. "Wenn va_end nicht aufgerufen wird, bevor eine Funktion, die va_start oder va_copy aufruft, zurückkehrt, ist das Verhalten undefiniert." - docs
Aaron McDaid
1
Sie sollten das Rückgabeergebnis von vasprintf überprüfen, da der Zeigerwert bei einem Fehler undefiniert ist. Fügen Sie also möglicherweise <neu> hinzu und fügen Sie Folgendes hinzu: if (size == -1) {throw std :: bad_alloc (); }
Neil McGill
Guter Punkt, ich habe die Antwort entsprechend geändert. Ich habe beschlossen, dort nur einen Kommentar einzufügen, anstatt das zu tun throw std::bad_alloc();, da ich in meiner Codebasis keine C ++ - Ausnahmen verwende. Für Leute, die dies tun, können sie ihn einfach basierend hinzufügen auf den Quellkommentar und Ihren Kommentar hier.
Thomas Perl
2

Dies ist der Code, den ich dazu in meinem Programm verwende ... Es ist nichts Besonderes, aber es macht den Trick ... Beachten Sie, dass Sie Ihre Größe gegebenenfalls anpassen müssen. MAX_BUFFER ist für mich 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;
}
Dave
quelle
4
Die Initialisierung von textString setzt bereits den gesamten Puffer auf Null. Keine Notwendigkeit zu memset ...
EricSchaefer
Dadurch wird eine vollständige zusätzliche Kopie der Daten erstellt, die vsnprintfdirekt in die Zeichenfolge verwendet werden kann.
Mooing Duck
2

Übernahm die Idee aus der Antwort von Dacav und Pixelpoint . Ich habe ein bisschen rumgespielt und das bekommen:

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

Mit vernünftiger Programmierpraxis glaube ich, dass der Code ausreichen sollte, aber ich bin immer noch offen für sicherere Alternativen, die immer noch einfach genug sind und kein C ++ 11 erfordern würden.


Und hier ist eine andere Version, die einen Anfangspuffer verwendet, um einen zweiten Aufruf zu verhindern, vsnprintf()wenn der Anfangspuffer bereits ausreicht.

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

(Es stellt sich heraus, dass diese Version der Antwort von Piti Ongmongkolkul ähnlich ist , nur dass sie nicht verwendet newund delete[]beim Erstellen auch eine Größe angibt std::string.

Die Idee hier ist, den Stack nicht über den Heap zu verwenden newund delete[]zu implizieren, da er keine Zuordnungs- und Freigabefunktionen aufrufen muss. Wenn er jedoch nicht ordnungsgemäß verwendet wird, kann es gefährlich sein, Überläufe in einigen (möglicherweise alten oder alten) zu puffern vielleicht nur anfällige) Systeme. Wenn dies ein Problem ist, empfehle ich dringend, stattdessen newund zu delete[]verwenden. Beachten Sie, dass das einzige Problem hier die Zuweisungen ist, wie sie vsnprintf()bereits mit Limits aufgerufen werden. Wenn Sie also ein Limit basierend auf der im zweiten Puffer zugewiesenen Größe angeben, werden diese ebenfalls verhindert.)

konsolebox
quelle
2

Ich benutze normalerweise Folgendes:

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

Nachteil: Nicht alle Systeme unterstützen Vasprint

Folkert van Heusden
quelle
vasprintf ist nett - Sie müssen jedoch den Rückkehrcode überprüfen. On -1 Puffer hat einen undefinierten Wert. Need: if (size == -1) {throw std :: bad_alloc (); }
Neil McGill
2

Unten leicht modifizierte Version der @ iFreilicht-Antwort, aktualisiert auf C ++ 14 (Verwendung der make_uniqueFunktion anstelle der Rohdeklaration ) und Unterstützung für std::stringArgumente hinzugefügt (basierend auf dem Artikel von 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";
}

Ausgabe:

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

Wenn Sie möchten, können Sie diese Antwort auch mit der Originalantwort zusammenführen.

Pawel Sledzikowski
quelle
1

Sie können die C ++ - Ausgabe in cout mithilfe der iomanip-Headerdatei formatieren. Stellen Sie sicher, dass Sie eine iomanip-Headerdatei einschließen, bevor Sie Hilfsfunktionen wie setprecision, setfill usw. verwenden.

Hier ist ein Codefragment, das ich in der Vergangenheit verwendet habe, um die durchschnittliche Wartezeit in dem Vektor zu drucken, den ich "akkumuliert" habe.

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

Hier finden Sie eine kurze Beschreibung, wie wir C ++ - Streams formatieren können. http://www.cprogramming.com/tutorial/iomanip.html

vinkris
quelle
1

Es kann Probleme geben, wenn der Puffer nicht groß genug ist, um die Zeichenfolge zu drucken. Sie müssen die Länge der formatierten Zeichenfolge bestimmen, bevor Sie dort eine formatierte Nachricht drucken. Ich mache einen eigenen Helfer dafür (getestet unter Windows und Linux GCC ), und Sie können versuchen, es zu verwenden.

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();
Valdemar_Rudolfovich
quelle
In Bezug auf die Zeile vsnprintf((char *)dst.data(), dst.size() + 1, format, ap);- Ist es sicher anzunehmen, dass der Puffer der Zeichenfolge Platz für ein abschließendes Nullzeichen hat? Gibt es Implementierungen, die keine Größe + 1 Zeichen zuweisen? Wäre es sicherer zu tundst.resize(length+1); vsnprintf((char *)dst.data(), dst.size(), format, ap); dst.resize(length);
drwatsoncode
Anscheinend lautet die Antwort auf meinen vorherigen Kommentar: Nein, es ist NICHT sicher anzunehmen, dass es ein Nullzeichen gibt. Insbesondere in Bezug auf die C ++ 98-Spezifikation: "Der Zugriff auf den Wert bei data () + size () führt zu undefiniertem Verhalten : Es gibt keine Garantie dafür, dass ein Nullzeichen die Zeichenfolge beendet, auf die der von dieser Funktion zurückgegebene Wert zeigt. Siehe Zeichenfolge :: c_str für eine Funktion, die eine solche Garantie bietet. Ein Programm darf keines der Zeichen in dieser Reihenfolge ändern. "Die C ++ 11-Spezifikation gibt dies jedoch an dataund c_strist ein Synonym.
Drwatsoncode
1
_return.desc = (boost::format("fail to detect. cv_result = %d") % st_result).str();
user5685202
quelle
1

Sehr sehr einfache Lösung.

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

Mir ist klar, dass dies schon oft beantwortet wurde, aber das ist prägnanter:

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

Beispiel:

#include <iostream>
#include <random>

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

Siehe auch http://rextester.com/NJB14150

Patrick Beard
quelle
1

UPDATE 1 : fmt::formatTests hinzugefügt

Ich habe meine eigenen Untersuchungen zu den hier eingeführten Methoden durchgeführt und dabei diametral entgegengesetzte Ergebnisse erzielt als hier erwähnt.

Ich habe 4 Funktionen über 4 Methoden verwendet:

  • Variadische Funktion + vsnprintf+std::unique_ptr
  • Variadische Funktion + vsnprintf+std::string
  • Variadic Template Funktion + std::ostringstream+ std::tuple+utility::for_each
  • fmt::formatFunktion aus der fmtBibliothek

Für das Test-Backend googletesthat das verwendet.

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

Die for_eachImplementierung erfolgt von hier aus: Über Tupel iterieren

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

}

Die Tests:

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

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

}

Ergebnisse :

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

Wie Sie sehen können, ist die Implementierung durch das vsnprintf+ std::stringgleich fmt::format, aber schneller als durch das vsnprintf+ std::unique_ptr, was schneller ist als durch das std::ostringstream.

Die Tests wurden in kompiliert Visual Studio 2015 Update 3und laufen auf Windows 7 x64 / Intel Core i7-4820K CPU @ 3.70GHz / 16GB.

Andry
quelle