std :: string.resize () и std :: string.length () - PullRequest
2 голосов
/ 21 мая 2010

Я относительно новичок в C ++, и я все еще начинаю понимать стандартную библиотеку C ++. Чтобы помочь переходу с C, я хочу отформатировать std::string, используя форматтеры в стиле printf. Я понимаю, что stringstream - это более безопасный для типов подход, но я считаю, что стиль printf гораздо легче читать и с ним работать (по крайней мере, на данный момент). Это моя функция:


using namespace std;

string formatStdString(const string &format, ...)
{
    va_list va;
    string output;
    size_t needed;
    size_t used;

    va_start(va, format);
    needed = vsnprintf(&output[0], 0, format.c_str(), va);
    output.resize(needed + 1); // for null terminator??
    va_end(va);    

    va_start(va, format);
    used = vsnprintf(&output[0], output.capacity(), format.c_str(), va);
    // assert(used == needed);
    va_end(va);

    return output;
}

Это работает, вроде. Несколько вещей, в которых я не уверен:

  1. Нужно ли освободить место для нулевого терминатора или это не нужно?
  2. Является ли capacity() правильной функцией для вызова здесь? Я продолжаю думать, что length() вернет 0, так как первый символ в строке - '\0'.

Иногда во время записи содержимого этой строки в сокет (с использованием c_str() и length()) на приемном конце выскакивают нулевые байты, что вызывает некоторое горе, но кажется, что они появляются непоследовательно. Если я вообще не использую эту функцию, нулевые байты не отображаются.

Ответы [ 7 ]

12 голосов
/ 21 мая 2010

При текущем стандарте (здесь стандарт отличается), нет никакой гарантии, что буфер внутренней памяти, управляемый std::string, будет непрерывным или что метод .c_str() возвращает указатель на внутреннее представление данных (Реализация может генерировать непрерывный блок только для чтения для этой операции и возвращать в нее указатель. Указатель на фактические внутренние данные можно получить с помощью метода .data() member, но обратите внимание, что он также возвращает постоянный указатель:оно не предназначено для изменения содержимого. Буфер, возвращаемый .data(), не обязательно завершается нулем, реализация должна гарантировать нулевое завершение только при вызове c_str(), так что даже в реализациях, где .data() иПри вызове .c_str() реализация может добавить \0 в конец буфера, когда вызывается последний.

Стандарт, предназначенный для реализации веревок, поэтому в принципе небезопасно делать то, что выпытаемся и из роВ соответствии со стандартом вы должны использовать промежуточное значение std::vector (гарантированное смежность, и есть гарантия, что &myvector[0] является указателем на первый выделенный блок реального буфера).

Во всех реализацияхЯ знаю, что внутренняя память, обработанная std::string, на самом деле является непрерывным буфером, и использование .data() - это неопределенное поведение (запись в постоянную переменную), но даже если оно некорректно, оно может работать (я бы этого избегал).Вам следует использовать другие библиотеки, разработанные для этой цели, например boost::format.

Об окончании NULL.Если вы наконец решите пойти по пути неопределенного ... вам нужно будет выделить дополнительное пространство для нулевого терминатора, так как библиотека запишет его в буфер.Теперь проблема в том, что в отличие от строк в стиле C, std::string s может содержать нулевые указатели внутри, поэтому вам придется изменить размер строки так, чтобы она соответствовала наибольшему непрерывному блоку памяти с самого начала, который не содержит \0.Вероятно, это проблема, которую вы обнаруживаете с ложными нулевыми символами.Это означает, что за неправильным подходом использования vsnprintf (или семейства) должен следовать str.resize( strlen( str.c_str() ) ), чтобы отбросить все содержимое строки после первого \0.

В целом, я бы посоветовал противэтот подход, и настаивайте на том, чтобы либо привыкнуть к способу форматирования C ++, используя сторонние библиотеки (boost - сторонняя, но также является самой стандартной нестандартной библиотекой), используя векторы или управляя памятью, как в C ...но этого последнего варианта следует избегать, как чумы.

// A safe way in C++ of using vsnprintf:
std::vector<char> tmp( 1000 ); // expected maximum size
vsnprintf( &tmp[0], tmp.size(), "Hi %s", name.c_str() ); // assuming name to be a string
std::string salute( &tmp[0] );
5 голосов
/ 21 мая 2010

Используйте boost::format, если вы предпочитаете printf() над потоками.

Редактировать: Просто чтобы прояснить это, на самом деле я полностью согласен с Аланом, который сказал, что вы должны использовать потоки.

2 голосов
/ 21 мая 2010

Я думаю, что нет никаких гарантий, что расположение строки, на которую ссылается & output [0], является смежным, и что вы можете записать в него.

Вместо этого используйте std :: vector вместо буфера, который гарантированно будет иметь непрерывное хранилище начиная с C ++ 03.

using namespace std;

string formatStdString(const string &format, ...)
{
    va_list va;
    vector<string::value_type> output(1); // ensure some storage is allocated
    size_t needed;
    size_t used;

    va_start(va, format);
    needed = vsnprintf(&output[0], 0, format.c_str(), va);
    output.resize(needed); // don't need null terminator
    va_end(va);    

    // Here we should ensure that needed != 0
    va_start(va, format);
    used = vsnprintf(&output[0], output.size(), format.c_str(), va); // use size()
    // assert(used == needed);
    va_end(va);

    return string(output.begin(), output.end());
}

ПРИМЕЧАНИЕ. Вам придется установить начальный размер для вектора, поскольку оператор & output [0] в противном случае может попытаться сослаться на несуществующий элемент (поскольку внутренний буфер еще не был выделен).

1 голос
/ 21 мая 2010

Моя реализация списков аргументов переменных для функций выглядит следующим образом:

std::string format(const char *fmt, ...)
{
  using std::string;
  using std::vector;

  string retStr("");

  if (NULL != fmt)
  {
     va_list marker = NULL;

     // initialize variable arguments
     va_start(marker, fmt);

     // Get formatted string length adding one for NULL
     size_t len = _vscprintf(fmt, marker) + 1;

     // Create a char vector to hold the formatted string.
     vector<char> buffer(len, '\0');
     int nWritten = _vsnprintf_s(&buffer[0], buffer.size(), len, fmt,
marker);

     if (nWritten > 0)
     {
        retStr = &buffer[0];
     }

     // Reset variable arguments
     va_end(marker);
  }

  return retStr;
}
0 голосов
/ 21 мая 2010

Чтобы помочь переходу с C, я хочу отформатировать std :: string используя форматтеры в стиле printf.

Только не надо: (

Если вы сделаете это, вы на самом деле не изучаете C ++, а программируете C с помощью компилятора C ++. Это плохое мышление, плохая практика, и оно распространяет проблемы, которые были созданы классами std::o*stream, чтобы избежать.

Я понимаю, что струнный поток является более типобезопасный подход, но я нахожу себя найти стиль printf гораздо проще читать и иметь дело (по крайней мере, для время).

Это не более типобезопасный подход. Это типичный подход. Более того, он минимизирует зависимости, уменьшает количество проблем, которые вы должны отслеживать (например, явное распределение буфера и отслеживание нулевого символьного терминатора), и упрощает поддержку вашего кода.

Кроме того, он полностью расширяемый / настраиваемый:

  • Вы можете расширить форматирование локали

  • вы можете определить операции ввода / вывода для пользовательских типов данных

  • Вы можете добавить новые типы форматирования вывода

  • вы можете добавить новые типы ввода / вывода буфера (например, сделать запись std :: clog в окно)

  • Вы можете подключить различные политики обработки ошибок.

std::o*stream Семейство классов очень мощное, и как только вы научитесь правильно его использовать, нет никаких сомнений, что вы не вернетесь.

Если у вас нет особых требований, ваше время, вероятно, будет гораздо лучше потрачено на изучение классов o * stream, чем написание printf на C ++.

0 голосов
/ 21 мая 2010

1) Вам не нужно выделять место для нулевого терминатора.
2) Capacity () говорит вам, сколько места зарезервировано для внутренней строки. length () сообщает вам длину строки. Вы, вероятно, не хотите емкость ()

0 голосов
/ 21 мая 2010

Класс std :: string заботится о нулевом терминаторе для вас.

Однако, как уже указывалось, поскольку вы используете vnsprintf для буфера необработанных базовых строк (анахронизмы умирают ...), вам нужно убедиться, что есть место для нулевого терминатора.

...