wake-up-neo.net

Sprintf mit std :: string in C++ verwenden

Ich verwende sprintf-Funktion in C++ 11 auf folgende Weise:

std::string toString()
{
    std::string output;
    uint32_t strSize=512;
    do
    {
        output.reserve(strSize);
        int ret = sprintf(output.c_str(), "Type=%u Version=%u ContentType=%u contentFormatVersion=%u magic=%04x Seg=%u",
            INDEX_RECORD_TYPE_SERIALIZATION_HEADER,
            FORAMT_VERSION,
            contentType,
            contentFormatVersion,
            magic,
            segmentId);

        strSize *= 2;
    } while (ret < 0);

    return output;
}

Gibt es eine bessere Möglichkeit, dies zu tun, als jedes Mal zu überprüfen, ob der reservierte Speicherplatz ausreichend ist? Für die zukünftige Möglichkeit, weitere Dinge hinzuzufügen.

12
daniel the man

Ihr Konstrukt - writing in den von c_str() erhaltenen Puffer ist undefiniertes Verhalten , auch wenn Sie zuvor die Kapazität des Strings überprüft haben. (Der Rückgabewert ist ein Zeiger auf const char, und die Funktion selbst ist aus einem Grund mit const markiert.)

Mischen Sie nicht C und C++, insbesondere nicht zum Schreiben in die interne Objektdarstellung. (Dies zerstört die grundlegende OOP.) Verwenden Sie C++, um den Typ zu schützen und keine Konvertierungsspezifizierer/-parameter zu stimmen, wenn nicht anders.

std::ostringstream s;
s << "Type=" << INDEX_RECORD_TYPE_SERIALIZATION_HEADER
  << " Version=" << FORMAT_VERSION
  // ...and so on...
  ;
std::string output = s.str();

Alternative:

std::string output = "Type=" + std::to_string( INDEX_RECORD_TYPE_SERIALIZATION_HEADER )
                   + " Version=" + std::to_string( FORMAT_VERSION )
                   // ...and so on...
                   ;
14
DevSolar

Die in anderen Antworten gezeigten C++ - Muster sind zwar netter, aber der Vollständigkeit halber ist hier mit sprintf ein korrekter Weg zu finden:

auto format = "your %x format %d string %s";
auto size = std::snprintf(nullptr, 0, format /* Arguments go here*/);
std::string output(size + 1, '\0');
std::sprintf(&output[0], format, /* Arguments go here*/);

Beachten

  • Sie müssen Ihre Zeichenfolge resize eingeben. reserve ändert die Größe des Puffers nicht. In meinem Beispiel konstruiere ich eine Zeichenfolge mit der richtigen Größe direkt.
  • c_str() gibt einen const char* zurück. Sie dürfen es nicht an sprintf übergeben.
  • Es ist nicht garantiert, dass std::string-Puffer vor C++ 11 zusammenhängend ist, und dies setzt diese Garantie voraus. Wenn Sie exotische Plattformen für Pre-C++ 11 unterstützen müssen, die die Implementierung des Seils für std::string verwenden, sollten Sie sprinting zuerst in std::vector<char> und dann den Vektor in den String kopieren.
  • Dies funktioniert nur, wenn die Argumente zwischen der Größenberechnung und der Formatierung nicht geändert werden. Verwenden Sie entweder lokale Kopien von Variablen oder Thread-Synchronisierungsprimitiven für Multithreadcode.
8
eerorika

Wir können den Code von hier https://stackoverflow.com/a/36909699/2667451 und hier https://stackoverflow.com/a/7257307 mischen.

template <typename ...Args>
std::string stringWithFormat(const std::string& format, Args && ...args)
{
    auto size = std::snprintf(nullptr, 0, format.c_str(), std::forward<Args>(args)...);
    std::string output(size + 1, '\0');
    std::sprintf(&output[0], format.c_str(), std::forward<Args>(args)...);
    return output;
}
1
zergeny

Ihr Code ist falsch. reserve weist Speicherplatz für die Zeichenfolge zu, ändert jedoch nicht ihre Größe. Durch das Schreiben in den von c_str zurückgegebenen Puffer wird auch die Größe nicht geändert. Der String glaubt also immer noch, dass seine Größe 0 ist, und Sie haben gerade etwas in den nicht verwendeten Speicherplatz des Puffers geschrieben. (Wahrscheinlich. Technisch gesehen hat der Code ein undefiniertes Verhalten, da das Schreiben in c_str undefiniert ist, sodass alles passieren kann).

Was Sie wirklich tun möchten, ist sprintf und ähnliche Funktionen im C-Stil zu vergessen und die C++ - Methode der String-Formatierung zu verwenden - String-Streams:

std::ostringstream ss;
ss << "Type=" << INDEX_RECORD_TYPE_SERIALIZATION_HEADER
   << " Version=" << FORAMT_VERSION
   << /* ... the rest ... */;
return ss.str();
1
Angew

Ein besserer Weg ist die Verwendung der {fmt} Bibliothek . Ex:

std::string message = fmt::sprintf("The answer is %d", 42);

Es bietet auch eine schönere Oberfläche als iostreams und printf. Ex:

std::string message = fmt::format("The answer is {}", 42);`

Sehen:
https://github.com/fmtlib/fmt
http://fmtlib.net/latest/api.html#printf-formatting-functions

1
Tavi Cacina

Ja da ist!

In C ist es besser, eine Datei mit dem null -Gerät zu verknüpfen und ein Dummy printf der gewünschten Ausgabe zu erstellen, um zu erfahren, wie viel Speicherplatz benötigt wird, wenn tatsächlich gedruckt wird. Weisen Sie dann den entsprechenden Puffer und sprintf die gleichen Daten zu.

In C++ können Sie auch den Ausgabestrom mit einem Nullgerät verknüpfen und die Anzahl der mit std :: ostream :: tellp gedruckten Zeichen testen. Die Verwendung von ostringstream ist jedoch eine bessere Lösung - siehe die Antworten von DevSolar oder Angew.

0
CiaPan