Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using sprintf with std::string in C++

Tags:

c++

string

c++11

I am using sprintf function in C++ 11, in the following way:

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

Is there a better way to do this, than to check every time if the reserved space was enough? For future possibility of adding more things.

like image 531
daniel the man Avatar asked Apr 28 '16 08:04

daniel the man


4 Answers

Your construct -- writing into the buffer received from c_str() -- is undefined behaviour, even if you checked the string's capacity beforehand. (The return value is a pointer to const char, and the function itself marked const, for a reason.)

Don't mix C and C++, especially not for writing into internal object representation. (That is breaking very basic OOP.) Use C++, for type safety and not running into conversion specifier / parameter mismatches, if for nothing else.

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...
                   ;
like image 72
DevSolar Avatar answered Oct 17 '22 03:10

DevSolar


The C++ patterns shown in other answers are nicer, but for completeness, here is a correct way with sprintf:

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*/);

Pay attention to

  • You must resize your string. reserve does not change the size of the buffer. In my example, I construct correctly sized string directly.
  • c_str() returns a const char*. You may not pass it to sprintf.
  • std::string buffer was not guaranteed to be contiguous prior to C++11 and this relies on that guarantee. If you need to support exotic pre-C++11 conforming platforms that use rope implementation for std::string, then you're probably better off sprinting into std::vector<char> first and then copying the vector to the string.
  • This only works if the arguments are not modified between the size calculation and formatting; use either local copies of variables or thread synchronisation primitives for multi-threaded code.
like image 15
eerorika Avatar answered Oct 17 '22 01:10

eerorika


We can mix code from here https://stackoverflow.com/a/36909699/2667451 and here https://stackoverflow.com/a/7257307 and result will be like that:

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;
}
like image 5
zergeny Avatar answered Oct 17 '22 02:10

zergeny


A better way is to use the {fmt} library. Ex:

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

It exposes also a nicer interface than iostreams and printf. Ex:

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

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

like image 2
Tavi Cacina Avatar answered Oct 17 '22 03:10

Tavi Cacina