formatowanie std :: string jak sprintf

454

Mam do formatu std::stringz sprintfi wysłać go do strumienia pliku. W jaki sposób mogę to zrobić?

Max Frai
źródło
6
długie opowiadanie krótkie zastosowanie boost::format(jak tutaj wykorzystuje rozwiązanie KennyTM ). boost::formatjuż obsługuje również operatorów strumieniowych C ++! Przykład: cout << format("helloworld. a=%s, b=%s, c=%s") % 123 % 123.123 % "this is a test" << endl;. boost::formatma najmniej wierszy kodu ... jest recenzowany i ładnie integruje się ze strumieniami C ++.
Trevor Boyd Smith,
@Ockonal - Ze względu na społeczność (nie dbam o mojego przedstawiciela) sugeruję zmianę wyboru. Ten, który jest obecnie wybrany, w pierwszym fragmencie, przedstawia błąd, który czeka na wystąpienie przy użyciu dowolnej maksymalnej długości. Drugi fragment całkowicie ignoruje wyrażone przez ciebie pragnienie używania vargów, takich jak sprintf. Sugeruję wybranie TYLKO odpowiedzi, która jest czysta, bezpieczna, opiera się tylko na standardach C ++, przetestowana i dobrze skomentowana. To, że jest moje, nie ma znaczenia. Jest to obiektywnie prawda. Zobacz stackoverflow.com/questions/2342162/… .
Douglas Daseeco
@TrevorBoydSmith a std::formatzostał dodany do C ++ 20 BTW: stackoverflow.com/a/57286312/895245 Awesome!
Ciro Santilli 31 冠状 病 六四 事件 法轮功
1
@CiroSantilli Przeczytałem artykuł o C++20wczoraj i zobaczyłem, że został C++20skopiowany boost(już po raz milionowy), dodając go std::formatdo C++20specyfikacji! Byłem bardzo, bardzo szczęśliwy! Wykorzystano prawie każdy plik C ++, który napisałem w ciągu ostatnich 9 lat boost::format. dodanie oficjalnego wyjścia w stylu printf do strumieni w C ++ przejdzie długą drogę IMO dla całego C ++.
Trevor Boyd Smith

Odpowiedzi:

332

Nie możesz tego zrobić bezpośrednio, ponieważ nie masz dostępu do zapisu do bufora bazowego (do C ++ 11; patrz komentarz Dietricha Eppa ). Najpierw musisz to zrobić ciągiem c, a następnie skopiować go do std :: string:

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

Ale nie jestem pewien, dlaczego po prostu nie używałbyś strumienia ciągów? Zakładam, że masz konkretne powody, aby nie tylko to zrobić:

  std::ostringstream stringStream;
  stringStream << "Hello";
  std::string copyOfStr = stringStream.str();
Doug T.
źródło
16
Magiczne ciasteczko char buf[100];sprawia, że ​​to rozwiązanie nie jest bardzo niezawodne. Ale najważniejszy pomysł już istnieje.
John Dibling,
18
John, strumienie nie są wolne. Jedynym powodem, dla którego strumienie wydają się być powolne, jest to, że domyślnie iostreamy synchronizują się z wyjściem C FILE, aby zmieszane cout i printfs były poprawnie wyprowadzane. Wyłączenie tego linku (z wywołaniem cout.sync_with_stdio (false)) powoduje, że strumienie c ++ przewyższają stdio, przynajmniej od MSVC10.
Jimbo,
72
Powodem użycia formatów jest umożliwienie lokalizatorowi przebudowania struktury zdania dla języków obcych, zamiast twardego kodowania gramatyki zdania.
Martijn Courteaux,
215
Z jakiegoś powodu inne języki używają składni podobnej do printf: Java, Python (nowa składnia jest jeszcze bliższa printf niż strumieniom). Tylko C ++ zadaje tę pełną obrzydliwość niewinnym ludziom.
quant_dev,
9
Jeszcze lepiej, użyj asprintf, który przydziela nowy ciąg z wystarczającą ilością miejsca do przechowywania wyniku. Następnie skopiuj to do std::stringjeśli chcesz i pamiętaj o freeoryginale. Możliwe jest również umieszczenie tego w makrze, aby każdy dobry kompilator pomógł ci sprawdzić format - nie chcesz umieszczać tam, doublegdzie %sjest oczekiwane
Aaron McDaid
286

Nowoczesne C ++ sprawia, że ​​jest to bardzo proste.

C ++ 20

C ++ 20 wprowadza std::format, co pozwala dokładnie to zrobić. Używa zastępczych pól podobnych do tych w Pythonie :

#include <iostream>
#include <format>

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

Sprawdź pełną dokumentację ! To ogromna poprawa jakości życia.


C ++ 11

Z c ++ 11 s std::snprintf, to już stało się bardzo łatwe i bezpieczne zadanie.

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

Powyższy fragment kodu jest licencjonowany na podstawie CC0 1.0 .

Objaśnienie linia po linii:

Cel: Napisz dochar*używając, std::snprintfa następnie przekonwertuj to nastd::string.

Najpierw określamy pożądaną długość tablicy znaków przy użyciu specjalnego warunku w snprintf. Od cppreference.com :

Zwracana wartość

[...] Jeśli wynikowy łańcuch zostanie obcięty z powodu limitu buf_size, funkcja zwraca całkowitą liczbę znaków (nie wliczając kończącego znaku zerowego), które zostałyby zapisane, gdyby limit nie został narzucony.

Oznacza to, że pożądany rozmiar to liczba znaków plus jeden , tak więc terminator zerowy usiądzie za wszystkimi innymi znakami i może zostać ponownie odcięty przez konstruktor łańcucha. Ten problem został wyjaśniony przez @ alexk7 w komentarzach.

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

snprintfzwróci liczbę ujemną, jeśli wystąpi błąd, więc sprawdzamy, czy formatowanie działało zgodnie z oczekiwaniami. Nieprzestrzeganie tego może prowadzić do cichych błędów lub przydzielenia ogromnego bufora, jak wskazał @ead w komentarzach.

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

Następnie przydzielamy nową tablicę znaków i przypisujemy ją do std::unique_ptr. Jest to ogólnie zalecane, ponieważ nie trzeba ręczniedelete ponownie tego .

Pamiętaj, że nie jest to bezpieczny sposób przydzielania unique_ptrtypów zdefiniowanych przez użytkownika, ponieważ nie można cofnąć przydziału pamięci, jeśli konstruktor zgłasza wyjątek!

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

Następnie możemy oczywiście po prostu użyć snprintfzgodnie z przeznaczeniem i zapisać sformatowany ciąg do char[].

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

Na koniec tworzymy i zwracamy nowe std::string, upewniając się, że na końcu pominięto terminator zerowy.

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

Można zobaczyć przykład w akcji tutaj .


Jeśli chcesz także użyć std::stringna liście argumentów, spójrz na tę treść .


Dodatkowe informacje o Visual Studio użytkowników :

Jak wyjaśniono w tej odpowiedzi , Microsoft zmienił nazwę std::snprintfna _snprintf(tak, bez std::). MS dodatkowo ustawiło to jako przestarzałe i zaleca użycie _snprintf_szamiast tego, jednak _snprintf_snie zaakceptuje bufora jako zera lub mniejszego niż sformatowane wyjście i nie obliczy długości wyjść, jeśli to nastąpi. Aby więc pozbyć się ostrzeżeń o wycofaniu podczas kompilacji, możesz wstawić następujący wiersz na górze pliku, który zawiera użycie _snprintf:

#pragma warning(disable : 4996)

Końcowe przemyślenia

Wiele odpowiedzi na to pytanie zostało napisanych przed C ++ 11 i używa stałych długości buforów lub vargs. Jeśli nie utkniesz ze starymi wersjami C ++, nie poleciłbym używania tych rozwiązań. Idealnie jest przejść na C ++ 20.

Ponieważ rozwiązanie C ++ 11 w tej odpowiedzi używa szablonów, może generować sporo kodu, jeśli jest często używane. Jednak jeśli nie tworzysz środowiska z bardzo ograniczoną przestrzenią na pliki binarne, nie będzie to stanowić problemu i nadal stanowi znaczną poprawę w stosunku do innych rozwiązań, zarówno pod względem przejrzystości, jak i bezpieczeństwa.

Jeśli wydajność przestrzeni jest bardzo ważna, te dwa rozwiązania z vargs i vsnprintf mogą być przydatne. NIE UŻYWAJ żadnych rozwiązań o ustalonych długościach buforów, które wymagają jedynie problemów.

iFreilicht
źródło
2
Podkreśl w swojej odpowiedzi dla użytkowników programu Visual Studio, że wersja VS musi być co najmniej 2013. Z tego artykułu wynika, że ​​działa ona tylko z wersją VS2013: Jeśli bufor jest wskaźnikiem zerowym, a liczba wynosi zero, len jest zwracany jako liczba znaków wymagana do sformatowania danych wyjściowych, nie licząc zera kończącego. Aby nawiązać połączenie z tym samym argumentem i parametrami ustawień regionalnych, przydziel bufor o długości co najmniej len + 1 znaków.
cha
3
@moooeeeep Wiele powodów. Po pierwsze, celem jest zwrócenie std :: string, a nie c-string, więc prawdopodobnie miałeś na myśli return string(&buf[0], size);lub coś podobnego. Po drugie, jeśli zwrócisz taki ciąg c, spowoduje to niezdefiniowane zachowanie, ponieważ wektor zawierający wartości, na które wskazujesz, zostanie unieważniony po powrocie. Po trzecie, kiedy zacząłem uczyć się C ++, standard nie określał, w jakiej kolejności elementy muszą być przechowywane wewnątrz std::vector, więc dostęp do jego pamięci za pomocą wskaźnika był nieokreślonym zachowaniem. Teraz to zadziała, ale nie widzę korzyści z robienia tego w ten sposób.
iFreilicht,
2
@iFreilicht Nowy std::stringzostanie zbudowany z niejawnie przekonwertowanego wektora ( inicjowanie kopii ), który jest następnie zwracany jako kopia, jak sugeruje podpis funkcji. Ponadto elementy a std::vectorsą i zawsze miały być przechowywane w sposób ciągły . Uważam jednak, że może to nie przynieść korzyści.
moooeeeep 27.04.2015
4
Naprawdę podoba mi się to rozwiązanie, ale myślę, że linia return string(buf.get(), buf.get() + size);powinna być w przeciwnym return string(buf.get(), buf.get() + size - 1);razie otrzymasz ciąg z zerowym znakiem na końcu. Stwierdziłem, że tak jest w przypadku gcc 4.9.
Phil Williams
3
Przekazywanie parametru std :: string do% s powoduje błąd kompilacji ( błąd: nie można przekazać obiektu nietypowego typu „std :: __ cxx11 :: basic_string <char>” przez funkcję variadic; wywołanie zostanie przerwane w czasie wykonywania [-Wnon-pod -varargs] ) w clang 3.9.1, ale w CL 19 kompiluje się dobrze i zamiast tego zawiesza się w czasie wykonywania. Jakąkolwiek flagę ostrzegawczą, którą mogę włączyć, aby mieć tę, która była brana pod uwagę podczas kompilacji w cl?
Zitrax
241

Rozwiązanie C ++ 11, które wykorzystuje vsnprintf()wewnętrznie:

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

Bezpieczniejsze i bardziej wydajne (przetestowałem to i jest szybsze) podejście:

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

fmt_strJest przekazywane przez wartość za zgodne z wymaganiamiva_start .

UWAGA: „Bezpieczniejsza” i „szybsza” wersja nie działa na niektórych systemach. Dlatego oba są nadal wymienione. Również „szybsze” zależy całkowicie od poprawności kroku wstępnego przydziału, w przeciwnym razie strcpyrenderowanie będzie wolniejsze.

Erik Aronesty
źródło
3
powolny. po co zwiększać rozmiar o 1? A kiedy ta funkcja zwraca -1?
0xDEAD WOŁOWINA
27
Nadpisujesz str.c_str ()? Czy to nie jest niebezpieczne?
kwantowo
8
va_start z argumentem referencyjnym ma problemy z MSVC. Nie działa cicho i zwraca wskaźniki do losowej pamięci. Aby obejść ten problem, użyj std :: string fmt zamiast std :: string & fmt lub napisz obiekt opakowujący.
Steve Hanov
6
Daję +1, ponieważ wiem, że to prawdopodobnie będzie działać w oparciu o to, jak większość std :: łańcuchów jest zaimplementowanych, jednak c_str nie jest tak naprawdę przeznaczony do modyfikowania bazowego łańcucha. To ma być tylko do odczytu.
Doug T.,
6
Aby uzyskać wcześniej wynikową długość łańcucha, zobacz: stackoverflow.com/a/7825892/908336 Nie widzę sensu sizew każdej iteracji, gdy można ją uzyskać przy pierwszym wywołaniu vsnprintf().
Massood Khaari,
107

boost::format() zapewnia pożądaną funkcjonalność:

Począwszy od streszczenia bibliotek formatu Boost:

Obiekt formatu jest konstruowany z ciągu formatu, a następnie otrzymuje argumenty poprzez powtarzane wywołania operatora%. Każdy z tych argumentów jest następnie konwertowany na ciągi, które z kolei są łączone w jeden ciąg, zgodnie z ciągiem formatującym.

#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
źródło
5
możesz również przycinać biblioteki, których potrzebujesz, również po przyspieszeniu. Za pomocą dostarczonego narzędzia.
Hassan Syed
7
Format wzmocnienia jest nie tylko duży, ale także bardzo wolny. Zobacz zverovich.net/2013/09/07/… i boost.org/doc/libs/1_52_0/libs/spirit/doc/html/spirit/karma/…
vitaut
14
Włączenie ulepszenia w dowolnym miejscu w projekcie natychmiast zwiększa znacznie czas kompilacji. W przypadku dużych projektów najprawdopodobniej nie ma to znaczenia. W przypadku małych projektów przyspieszenie jest trudne.
quant_dev,
2
@vitaut Podczas gdy jest to strasznie pochłaniające zasoby w porównaniu do alternatyw. Jak często formatujesz ciągi? Biorąc pod uwagę, że zajmuje to tylko kilka mikrosekund, a większość projektów prawdopodobnie używa go tylko kilkadziesiąt razy, nie jest to zauważalne w projekcie, który nie koncentruje się zbytnio na formatowaniu ciągów, prawda?
AturSams,
2
Niestety, boost :: format nie działa w ten sam sposób: nie akceptuje var_args. Niektórzy ludzie lubią mieć cały kod związany z jednym programem wyglądający tak samo / używając tych samych idiomów.
xor007,
88

C ++ 20 obejmie, std::formatktóry przypomina sprintfAPI, ale jest w pełni bezpieczny dla typów, działa z typami zdefiniowanymi przez użytkownika i używa składni łańcuchowej formatu podobnego do Pythona. Oto, w jaki sposób będziesz mógł sformatować std::stringi zapisać go w strumieniu:

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

lub

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

Alternatywnie możesz użyć biblioteki {fmt}, aby sformatować ciąg i zapisać go stdoutlub strumień plików za jednym razem:

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

Podobnie jak w przypadku sprintfwiększości innych odpowiedzi tutaj, niestety używają one varargs i są z natury niebezpieczne, chyba że użyjesz czegoś takiego jak formatatrybut GCC, który działa tylko z literałymi ciągami formatu. Możesz zobaczyć, dlaczego te funkcje są niebezpieczne na następującym przykładzie:

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

gdzie string_format jest wdrożenie z odpowiedzi Erika Aronesty. Ten kod się kompiluje, ale najprawdopodobniej ulegnie awarii podczas próby uruchomienia:

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

Zastrzeżenie : Jestem autorem {fmt} i C ++ 20 std::format.

vitaut
źródło
IMHO tęsknisz za error: 'fmt' has not been declared
Sérgio
To tylko fragment kodu, a nie pełny kod. Oczywiście musisz dołączyć <fmt / format.h> i umieścić kod w funkcji.
vitaut
dla mnie nie jest to takie oczywiste, IMHO powinieneś umieścić to we fragmencie, dzięki za opinie
Sérgio
1
Podobna fmtimplementacja została dodana do C ++ 20! stackoverflow.com/a/57286312/895245 fmt obecnie twierdzi, że ma wsparcie. Świetna robota!
Ciro Santilli 31 冠状 病 六四 事件 法轮功 法轮功
2
@vitaut Dziękujemy za pracę nad tym!
Curt Nichols,
18

Jeśli chcesz tylko składnię podobną do printf (bez samodzielnego wywoływania printf), spójrz na Format Boost .

Timo Geusch
źródło
Dodanie całej biblioteki dla tak prostej rzeczy nie jest konieczne. Ten odpowiedział na stackoverflow.com/questions/19009094/... .
Douglas Daseeco
15

Napisałem własny przy użyciu vsnprintf, więc zwraca ciąg zamiast konieczności tworzenia własnego bufora.

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

Możesz więc używać go jak

std::string mystr = format("%s %d %10.5f", "omg", 1, 10.5);
Piti Ongmongkolkul
źródło
Robi to pełną dodatkową kopię danych, możliwe jest użycie vsnprintfbezpośrednio w ciągu.
Mooing Duck
1
Użyj kodu z stackoverflow.com/a/7825892/908336, aby wcześniej uzyskać wynikową długość łańcucha. I możesz użyć inteligentnych wskaźników do kodu bezpiecznego dla wyjątków:std::unique_ptr<char[]> buffer (new char[size]);
Massood Khaari
Nie jestem pewien, czy jest to poprawne w przypadku awarii; Myślę, że musisz zrobić va_copy vl dla drugiego vsnprintf (), aby poprawnie zobaczyć argumenty. Na przykład patrz: github.com/haberman/upb/blob/…
Josh Haberman
15

Aby sformatować std::stringw sposób „sprintf”, wywołaj snprintf(argumenty nullptri 0), aby uzyskać potrzebną długość bufora. Napisz swoją funkcję przy użyciu szablonu variadic C ++ 11 w następujący sposób:

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

Kompiluj z obsługą C ++ 11, na przykład w GCC: g++ -std=c++11

Stosowanie:

  std::cout << string_sprintf("%g, %g\n", 1.23, 0.001);
użytkownik2622016
źródło
std :: snprintf nie jest dostępny w VC ++ 12 (Visual Studio 2013). Zamiast tego zamień go na _snprintf.
Shital Shah,
dlaczego nie używasz char buf[length + 1];zamiast char* buf = new char[length + 1];?
Behrouz.M,
Różnica między używaniem char[]a char*nowością polega na tym, że w pierwszym przypadku buf byłby przydzielany na stosie. Jest odpowiedni dla małych buforów, ale ponieważ nie możemy zagwarantować rozmiaru wynikowego łańcucha, nieco lepiej jest użyć new. Na przykład na mojej maszynie string_sprintf("value: %020000000d",5)wypisz skandaliczną liczbę zer wiodących przed liczbą 5, zrzuty rdzenia podczas używania tablicy na stosie, ale działa OK, gdy używasz dynamicznie przydzielonej tablicynew char[length + 1]
2622016
bardzo sprytny pomysł, aby uzyskać rzeczywisty rozmiar wzmocnienia potrzebny do sformatowanego wyjścia
Chris
1
@ user2622016: Dzięki za rozwiązanie! Pamiętaj, że std::move jest to zbyteczne .
Mihai Todor
14

[edycja: 20/05/25] lepiej jeszcze ...:
W nagłówku:

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

Funkcja PRINTSTRING(r)służy do obsługi interfejsu graficznego lub terminala lub innych specjalnych potrzeb wyjściowych #ifdef _some_flag_, domyślnie:

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

[edytuj '17 / 8/31] Dodanie variadic wersji szablonów „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;
}

która jest w rzeczywistości wersją rozdzielaną przecinkami (zamiast) niekiedy przeszkadzających <<operatorów, używanych w ten sposób:

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


[edytuj] Dostosowany do wykorzystania techniki zawartej w odpowiedzi Erika Aronesty (powyżej):

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

[poprzednia odpowiedź]
Bardzo późna odpowiedź, ale dla tych, którzy, podobnie jak ja, lubią „sprintf”: napisałem i używam następujących funkcji. Jeśli Ci się spodoba, możesz rozwinąć opcje%, aby ściślej pasowały do ​​sprintfów; te tam obecnie są wystarczające na moje potrzeby. Używasz stringf () i stringfappend () tak samo jak sprintf. Pamiętaj tylko, że parametry ... muszą być typami 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;
}
slashmais
źródło
@MooingDuck: Zmieniono parametr funkcji zgodnie z komentarzem Dana do odpowiedzi Aronesty. Używam tylko Linux / gcc, a fmtjako odniesienie działa dobrze. (Ale przypuszczam, że ludzie będą chcieli bawić się zabawkami, więc ...) Jeśli są jakieś inne domniemane „błędy”, czy mógłbyś to rozwinąć?
slashmais
Źle zrozumiałem, jak działała część jego kodu i myślałem, że ma to wpływ na wiele rozmiarów. Ponowne badanie pokazuje, że się pomyliłem. Twój kod jest poprawny.
Mooing Duck 12.03.13
Z odpowiedzi Erika Aronesty'ego wynika czerwony śledź. Jego pierwsza próbka kodu jest niebezpieczna, a jego druga jest nieefektywna i niezdarna. Czysta implementacja jest wyraźnie wskazana przez fakt, że jeśli rozmiar buf_siz dowolnej funkcji rodziny vprintf wynosi zero, nic nie jest zapisywane, a bufor może być wskaźnikiem zerowym, jednak zwracana wartość (liczba bajtów, które zostałyby zapisane bez uwzględnienia terminator zerowy) jest nadal obliczany i zwracany. Odpowiedź dotycząca jakości produkcji znajduje się tutaj: stackoverflow.com/questions/2342162/…
Douglas Daseeco
10

Oto jak robi to Google: StringPrintf(licencja BSD)
i Facebook robi to w dość podobny sposób: StringPrintf(licencja Apache)
Oba zapewniają również wygodę StringAppendF.

PW.
źródło
10

Moje dwa centy za to bardzo popularne pytanie.

Cytując manpage z printf-Jak funkcji :

Po pomyślnym zwróceniu funkcje te zwracają liczbę drukowanych znaków (z wyłączeniem bajtu zerowego używanego do zakończenia wyjścia w łańcuchach).

Funkcje snprintf () i vsnprintf () nie zapisują więcej niż wielkości bajtów (w tym kończącego bajtu zerowego ('\ 0')). Jeśli wynik został obcięty z powodu tego limitu, zwracaną wartością jest liczba znaków (z wyłączeniem kończącego bajtu zerowego), które zostałyby zapisane w końcowym ciągu, gdyby było wystarczająco dużo miejsca. Dlatego zwracana wartość rozmiaru lub większa oznacza, że ​​dane wyjściowe zostały obcięte.

Innymi słowy, rozsądna implementacja C ++ 11 powinna wyglądać następująco:

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

Działa całkiem dobrze :)

Szablony Variadic są obsługiwane tylko w C ++ 11. Odpowiedź z pixelpoint pokazuje podobną technikę przy użyciu starszych stylów programowania.

To dziwne, że C ++ nie ma czegoś takiego po wyjęciu z pudełka. Ostatnio dodali to_string(), co moim zdaniem jest wielkim krokiem naprzód. Zastanawiam się, czy dodadzą one .formatoperatora dostd::string ostatecznie ...

Edytować

Jak wskazał alexk7, A +1jest potrzebne na wartości zwracanej std::snprintf, ponieważ musimy mieć miejsce na \0bajt. Intuicyjnie, w przypadku większości architektur brakujące +1spowoduje requiredczęściowe zastąpienie liczby całkowitej przez 0. Stanie się tak po oszacowaniu requiredrzeczywistego parametru dlastd::snprintf , więc efekt nie powinien być widoczny.

Problem ten może się jednak zmienić, na przykład przy optymalizacji kompilatora: co, jeśli kompilator zdecyduje się użyć rejestru dla requiredzmiennej? Jest to rodzaj błędów, które czasami powodują problemy z bezpieczeństwem.

Dacav
źródło
1
snprintf zawsze dołącza kończący zerowy bajt, ale zwraca liczbę znaków bez niego. Czy ten kod nie zawsze pomija ostatni znak?
alexk7
@ alexk7, Nice catch! Aktualizuję odpowiedź. Kod nie pomija ostatniego znaku, ale pisze poza końcem bytesbufora, prawdopodobnie ponad requiredliczbą całkowitą (która na szczęście w tym momencie jest już oceniana).
Dacav
1
Tylko mała wskazówka: przy rozmiarze bufora 0 możesz przekazać nullptrargument jako bufor, eliminując char b;wiersz w kodzie. ( Źródło )
iFreilicht,
@iFreilicht, fix'd. Również +1
Dacav
2
Użycie „char bajtów [wymagane]” zostanie przydzielone na stosie zamiast na stercie, może być niebezpieczne w przypadku ciągów wielkoformatowych. Rozważ użycie zamiast tego nowego. 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;
}

Korzystanie z C99 snprintf i C ++ 11

aaa
źródło
8

Testowane, odpowiedź na pytanie o jakość produkcji

Ta odpowiedź dotyczy ogólnego przypadku przy użyciu technik zgodnych ze standardami. To samo podejście podano jako przykład na stronie CppReference.com u dołu strony. W przeciwieństwie do ich przykładów, ten kod spełnia wymagania pytania i jest testowany w terenie w robotyce i aplikacjach satelitarnych. Poprawiono także komentowanie. Jakość projektu omówiono poniżej.

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

Przewidywalna wydajność liniowa

Dwa przejścia są niezbędne dla bezpiecznej, niezawodnej i przewidywalnej funkcji wielokrotnego użytku zgodnie ze specyfikacją pytania. Domniemania dotyczące rozkładu wielkości vargów w funkcji wielokrotnego użytku są złym stylem programowania i należy ich unikać. W tym przypadku arbitralnie duże reprezentacje zmiennej długości vargs są kluczowym czynnikiem przy wyborze algorytmu.

Ponowna próba przepełnienia jest wykładniczo nieefektywna, co jest kolejnym powodem omawianym, gdy komitet normalizacyjny C ++ 11 omówił powyższą propozycję zapewnienia suchego przebiegu, gdy bufor zapisu jest zerowy.

W powyższej implementacji gotowej do produkcji, pierwszy przebieg jest takim suchym przebiegiem w celu ustalenia wielkości alokacji. Alokacja nie występuje. Przetwarzanie dyrektyw printf i czytanie vargów stało się niezwykle skuteczne przez dziesięciolecia. Kod wielokrotnego użytku powinien być przewidywalny, nawet jeśli trzeba poświęcić niewielką nieefektywność dla trywialnych przypadków.

Bezpieczeństwo i niezawodność

Andrew Koenig powiedział niewielkiej grupie z nas po swoim wykładzie na wydarzeniu w Cambridge: „Funkcje użytkownika nie powinny polegać na wykorzystaniu niepowodzenia wyjątkowej funkcjonalności”. Od tego czasu jego mądrość okazała się prawdziwa. Naprawione i zamknięte błędy bezpieczeństwa często wskazują na powtórne włamania w opisie otworu wykorzystanego przed naprawą.

Jest to wspomniane w formalnej propozycji zmiany standardów dla funkcji bufora zerowego w Alternative to sprintf, C9X Revision Wniosek , Dokument ISO IEC WG14 N645 / X3J11 96-008 . Dowolnie długi ciąg wstawiany według dyrektywy drukowania „% s” w ramach ograniczeń dynamicznej dostępności pamięci nie jest wyjątkiem i nie powinien być wykorzystywany do tworzenia „wyjątkowej funkcjonalności”.

Rozważ propozycję obok przykładowego kodu podanego na dole strony C ++ Reference.org, do której link znajduje się w pierwszym akapicie tej odpowiedzi.

Ponadto testowanie przypadków awarii rzadko jest tak wiarygodne jak przypadki sukcesu.

Ruchliwość

Wszyscy główni dostawcy systemów operacyjnych udostępniają kompilatory w pełni obsługujące std :: vsnprintf jako część standardów c ++ 11. Hosty z produktami dostawców, które nie utrzymują już dystrybucji, powinny być wyposażone w g ++ lub clang ++ z wielu powodów.

Wykorzystanie w stosie

Wykorzystanie stosu w pierwszym wywołaniu do std :: vsnprintf będzie mniejsze lub równe zużyciu drugiego i zostanie zwolnione przed rozpoczęciem drugiego wywołania. Jeśli pierwsze wywołanie przekroczy dostępność stosu, std :: fprintf również się nie powiedzie.

Douglas Daseeco
źródło
Krótki i solidny. Może to zawieść na HP-UX, IRIX, Tru64, które mają niezgodne vsnprintf-s. EDYCJA: również biorąc pod uwagę, w jaki sposób dwa przejścia mogą wpłynąć na wyniki, szczególnie. czy w przypadku najpopularniejszego formatowania małych ciągów zastanawiałeś się, czy początkowe przejście może być wystarczająco duże?
Inżynier
FWIW, zgadywanie, o którym mówiłem, wykorzystuje bufor przydzielony do stosu, w którym następuje pierwsze uruchomienie. Jeśli pasuje, oszczędza koszt drugiego uruchomienia i dynamicznej alokacji, która tam występuje. Przypuszczalnie małe struny są częściej używane niż duże. W moim prymitywnym teście strategia ta (prawie) zmniejsza o połowę czas działania dla małych łańcuchów i mieści się w granicach kilku procent (może być ustalonych narzutów?) Powyżej strategii. Czy mógłbyś rozwinąć projekt C ++ 11, który wykorzystuje suchobieg itp.? Chciałbym o tym przeczytać.
Inżynier
@Engineerist, twoje pytania zostały zawarte w treści odpowiedzi, powyżej i poniżej kodu. W ten sposób można ułatwić czytanie podtematów.
Douglas Daseeco
6

C ++ 20 std::format

Przybył! Funkcja jest opisana na stronie : http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0645r9.html i używa .format()składni podobnej do Pythona .

Oczekuję, że użycie będzie wyglądało tak:

#include <format>
#include <string>

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

Spróbuję, kiedy wsparcie dotrze do GCC, GCC 9.1.0 g++-9 -std=c++2anadal go nie obsługuje.

Interfejs API doda nowy std::formatnagłówek:

Proponowany interfejs API formatowania jest zdefiniowany w nowym nagłówku <format>i nie powinien mieć wpływu na istniejący kod.

Istniejąca fmtbiblioteka twierdzi, że zaimplementuje ją, jeśli potrzebujesz wypełnienia: https://github.com/fmtlib/fmt

Implementacja C ++ 20 std::format.

i był wcześniej wspomniany w: formatowanie std :: string jak sprintf

Ciro Santilli
źródło
5

Na podstawie odpowiedzi udzielonej przez Erika 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();
}

Pozwala to uniknąć konieczności odrzucania, constktórego wynik .c_str()był w pierwotnej odpowiedzi.

Chety
źródło
1
Z odpowiedzi Erika Aronesty'ego wynika czerwony śledź. Jego pierwsza próbka kodu jest niebezpieczna, a druga, z pętlą nieefektywną i niezdarną. Czysta implementacja jest wyraźnie wskazana przez fakt, że jeśli rozmiar buf_siz dowolnej funkcji rodziny vprintf wynosi zero, nic nie jest zapisywane, a bufor może być wskaźnikiem zerowym, jednak zwracana wartość (liczba bajtów, które zostałyby zapisane bez uwzględnienia terminator zerowy) jest nadal obliczany i zwracany. Odpowiedź dotycząca jakości produkcji znajduje się tutaj: stackoverflow.com/questions/2342162/…
Douglas Daseeco
Odpowiedź Erika Aronestya była edytowana od czasu dodania mojej. Chciałem podświetlić opcję użycia wektora <char> do przechowywania napisów podczas ich budowania. Często używam tej techniki, wywołując funkcje C z kodu C ++. Interesujące jest to, że pytanie ma teraz 34 odpowiedzi.
ChetS,
Przykład cppreference.com na stronie vfprintf został dodany później. Uważam, że najlepszą odpowiedzią jest obecnie akceptowana odpowiedź, użycie strumieni ciągów zamiast wariantu printf jest sposobem na C ++. Jednak moja odpowiedź dodała wartość, gdy została udzielona; Było to stopniowo lepsze niż inne odpowiedzi w tym czasie. Teraz standard ma string_view, pakiety parametrów i szablon Variadic, nowa odpowiedź może obejmować te funkcje. Jeśli chodzi o moją odpowiedź, chociaż może ona już nie zasługiwać na dodatkowe głosy w górę, nie zasługuje na to, aby została skreślona lub odrzucona, dlatego pozostawiam ją taką.
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);
}
punkt pikselowy
źródło
1
+1 za inteligentny pomysł, ale nie jest jasne, co _vscprintfto jest. Myślę, że powinieneś rozwinąć tę odpowiedź.
Dacav
3

string nie ma tego, czego potrzebujesz, ale ma std :: stringstream. Użyj ciągu znaków, aby utworzyć ciąg, a następnie wyodrębnij ciąg. Oto pełna lista rzeczy, które możesz zrobić. Na przykład:

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

da Ci 10 miejsc po przecinku z precyzją podczas drukowania podwójnego lub zmiennoprzecinkowego.

Hassan Syed
źródło
8
co wciąż nie daje ci nic w pobliżu kontrolki, którą daje printf ... ale jest fajne.
Erik Aronesty
3

Możesz spróbować:

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
źródło
3

Jeśli korzystasz z systemu, który ma asprintf (3) , możesz łatwo go owinąć:

#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
źródło
2
Dodałbym wcześniej ten wiersz jako deklarację format, ponieważ mówi gcc, aby sprawdził typy argumentów i dał przyzwoite ostrzeżenie za pomocą -Wall:std::string format(const char *fmt, ...) __attribute__ ((format (printf, 1, 2)));
Aaron McDaid
2
Właśnie dodałem połączenie do va_end. „jeśli va_end nie zostanie wywołany przed powrotem funkcji wywołującej va_start lub va_copy, zachowanie jest niezdefiniowane.” - docs
Aaron McDaid
1
Powinieneś sprawdzić wynik vasprintf, ponieważ wartość wskaźnika jest niezdefiniowana w przypadku niepowodzenia. Więc ewentualnie dołącz <new> i dodaj: if (size == -1) {throw std :: bad_alloc (); }
Neil McGill
Dobra uwaga, odpowiednio zmodyfikowałem odpowiedź, postanowiłem po prostu wstawić tam komentarz zamiast tego throw std::bad_alloc();, ponieważ nie używam wyjątków C ++ w mojej bazie kodu, a dla ludzi, którzy to robią, mogą łatwo dodać go na podstawie na komentarz źródłowy i twój komentarz tutaj.
Thomas Perl,
2

To jest kod, którego używam do zrobienia tego w moim programie ... To nic nadzwyczajnego, ale robi to sztuczkę ... Uwaga, będziesz musiał dostosować swój rozmiar odpowiednio do potrzeb. MAX_BUFFER to dla mnie 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
źródło
4
Inicjalizacja textString już ustawia cały bufor na zero. Nie trzeba
zapamiętywać
Robi to pełną dodatkową kopię danych, możliwe jest użycie vsnprintfbezpośrednio w ciągu.
Mooing Duck
2

Wziął pomysł z Dacav i odpowiedź pixelpoint . Grałem trochę i dostałem to:

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

Z rozsądną praktyką programowania uważam, że kod powinien wystarczyć, jednak wciąż jestem otwarty na bezpieczniejsze alternatywy, które są wciąż wystarczająco proste i nie wymagałyby C ++ 11.


A oto kolejna wersja, która wykorzystuje bufor początkowy, aby zapobiec ponownemu wywołaniu, vsnprintf()gdy bufor początkowy jest już wystarczający.

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

(Okazuje się, że ta wersja jest po prostu podobna do odpowiedzi Piti Ongmongkolkul , tyle że nie używa newi delete[]określa rozmiar podczas tworzenia std::string.

Chodzi o to, aby nie używać newi delete[]sugerować użycie stosu nad stertą, ponieważ nie musi on wywoływać funkcji alokacji i dezalokacji, jednak jeśli nie zostanie właściwie użyty, buforowanie przepełnień w niektórych (być może starych lub być może po prostu słabe) systemy. Jeśli jest to problem, zdecydowanie sugeruję użycie newi delete[]zamiast tego. Zauważ, że jedyny problem dotyczy alokacji, jak vsnprintf()to się już nazywa z limitami, więc określenie limitu na podstawie wielkości przydzielonej w drugim buforze również by je zapobiegło.)

konsolebox
źródło
2

Zwykle używam tego:

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

Wada: nie wszystkie systemy obsługują vasprint

Folkert van Heusden
źródło
vasprintf jest fajny - jednak musisz sprawdzić kod powrotu. Bufor na -1 będzie miał nieokreśloną wartość. Potrzebujesz: if (size == -1) {throw std :: bad_alloc (); }
Neil McGill
2

Poniżej nieznacznie zmodyfikowana wersja odpowiedzi @iFreilicht, zaktualizowana do C ++ 14 (użycie make_uniquefunkcji zamiast surowej deklaracji) i dodano obsługę std::stringargumentów (na podstawie artykułu Kenny'ego Kerra )

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

Wynik:

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

W razie potrzeby możesz połączyć tę odpowiedź z oryginalną.

Paweł Śledzikowski
źródło
1

Możesz sformatować dane wyjściowe C ++ w cout przy użyciu pliku nagłówka iomanip. Upewnij się, że dołączasz plik nagłówka iomanip, zanim użyjesz funkcji pomocniczych, takich jak setprecision, setfill itp.

Oto fragment kodu, którego użyłem w przeszłości, aby wydrukować średni czas oczekiwania w wektorze, który „skumulowałem”.

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

Oto krótki opis tego, jak możemy sformatować strumienie C ++. http://www.cprogramming.com/tutorial/iomanip.html

Vinkris
źródło
1

Mogą wystąpić problemy, jeśli bufor nie jest wystarczająco duży, aby wydrukować ciąg. Musisz określić długość sformatowanego ciągu przed wydrukowaniem tam sformatowanej wiadomości. Robię do tego własnego pomocnika (testowane na Windows i Linux GCC ) i możesz go użyć.

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
źródło
W odniesieniu do linii vsnprintf((char *)dst.data(), dst.size() + 1, format, ap);- czy można bezpiecznie założyć, że bufor łańcucha ma miejsce na kończący znak null? Czy istnieją implementacje, które nie przydzielają rozmiaru + 1 znaków. Czy byłoby to bezpieczniejszedst.resize(length+1); vsnprintf((char *)dst.data(), dst.size(), format, ap); dst.resize(length);
drwatsoncode
Najwyraźniej odpowiedź na mój poprzedni komentarz brzmi: Nie, nie jest bezpiecznie zakładać, że znak jest pusty. W szczególności w odniesieniu do specyfikacji C ++ 98: „Dostęp do wartości w data () + size () powoduje niezdefiniowane zachowanie : Nie ma gwarancji, że znak pusty zakończy sekwencję znaków wskazywaną przez wartość zwracaną przez tę funkcję. Zobacz łańcuch :: c_str dla funkcji, która zapewnia taką gwarancję. Program nie zmienia żadnych znaków w tej sekwencji. ”Jednak specyfikacja C ++ 11 wskazuje na to datai c_strsą synonimami.
drwatsoncode
1
_return.desc = (boost::format("fail to detect. cv_result = %d") % st_result).str();
użytkownik5685202
źródło
1

Bardzo, bardzo proste rozwiązanie.

std::string strBuf;
strBuf.resize(256);
int iCharsPrinted = sprintf_s((char *)strPath.c_str(), strPath.size(), ...);
strBuf.resize(iCharsPrinted);
Basza
źródło
1

Zdaję sobie sprawę, że na to pytanie udzielono odpowiedzi wiele razy, ale jest to bardziej zwięzłe:

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

przykład:

#include <iostream>
#include <random>

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

Zobacz także http://rextester.com/NJB14150

Patrick Beard
źródło
1

AKTUALIZACJA 1 : dodanofmt::format testy

Przeprowadziłem własne badanie wokół metod, które tu wprowadziłem i uzyskałem diametralnie przeciwne wyniki w porównaniu z wymienionymi tutaj.

Użyłem 4 funkcji w 4 metodach:

  • funkcja wariadyczna + vsnprintf+std::unique_ptr
  • funkcja wariadyczna + vsnprintf+std::string
  • funkcja szablonu variadic + std::ostringstream+ std::tuple+utility::for_each
  • fmt::formatfunkcja z fmtbiblioteki

Do testowania zaplecza googletestużył.

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

for_eachRealizacja pochodzi stąd: iteracyjne nad krotki

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

}

Testy:

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

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

}

WYNIKI :

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

Jak widać implementacja przez vsnprintf+ std::stringjest równa fmt::format, ale szybsza niż przez vsnprintf+std::unique_ptr , co jest szybsze niż przez std::ostringstream.

Testy zostały skompilowane Visual Studio 2015 Update 3i uruchomione w Windows 7 x64 / Intel Core i7-4820K CPU @ 3.70GHz / 16GB.

Andry
źródło