Использование sprintf с std::string в С++

Я использую функцию sprintf в С++ 11 следующим образом:

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

Есть ли лучший способ сделать это, чем проверять каждый раз, если зарезервированного пространства было достаточно? Для будущей возможности добавить больше вещей.

Ответ 1

Ваша конструкция - запись в буфер, полученный из c_str(), - это undefined поведение, даже если вы предварительно проверили пропускную способность строки. (Возвращаемое значение является указателем на const char, а сама функция помечает const.)

Не смешивайте C и С++, особенно не для записи во внутреннее представление объекта. (Это нарушает очень простой OOP.) Используйте С++, для безопасности типов и не выполняйте ошибки спецификации/несоответствия параметров, если ничего не делать.

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

Альтернатива:

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

Ответ 2

Шаблоны С++, показанные в других ответах, более приятные, но для полноты, здесь правильный путь с 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*/);

Обратите внимание на

  • Вы должны resize указать свою строку. reserve не изменяет размер буфера. В моем примере я строю правильно размерную строку напрямую.
  • c_str() возвращает a const char*. Вы не можете передать его sprintf. Буфер
  • std::string не должен быть смежным до С++ 11, и это зависит от этой гарантии. Если вам нужно поддерживать экзотические pre-С++ 11 соответствующие платформы, которые используют реализацию веревки для std::string, то вам, вероятно, лучше сперва сначала перейти в std::vector<char>, а затем скопировать вектор в строку.
  • Это работает только в том случае, если аргументы не изменяются между вычислением размера и форматированием; используйте либо локальные копии переменных, либо примитивы синхронизации потоков для многопоточного кода.

Ответ 3

Ваш код неверен. reserve выделяет память для строки, но не меняет ее размер. Запись в буфер, возвращаемый c_str, также не изменяет его размер. Поэтому строка по-прежнему считает, что ее размер равен 0, и вы только что что-то написали в неиспользуемом пространстве в буфере строк. (Возможно, с технической точки зрения код имеет Undefined Behavior, потому что запись в c_str есть undefined, так что все может случиться).

То, что вы действительно хотите сделать, - это забыть sprintf и подобные функции C-стиля, а также использовать способ строкового форматирования строк и строк в формате С++:

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

Ответ 5

Мы можем смешать код отсюда fooobar.com/questions/557278/... и здесь fooobar.com/questions/44659/..., и результат будет подобен что:

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

Ответ 6

Да, есть!

В C лучший способ - связать файл с нулевым устройством и сделать фиктивный printf желаемого вывода на него, чтобы узнать, сколько места потребуется, если оно будет напечатано. Затем выделите соответствующий буфер и sprintf те же самые данные.

В С++ вы также можете связать выходной поток с нулевым устройством и проверить количество символов, напечатанных с помощью std:: ostream:: tellp. Однако использование ostringstream является лучшим решением - см. Ответы DevSolar или Angew.