Есть ли аналогичная функция к `vsnprintf`, которая работает с` std :: string` - PullRequest
1 голос
/ 29 марта 2019

Я не вижу конструктора для std::string, который может потреблять va_list.Есть ли общее решение для преобразования va_list в std::string?

Я видел решения в виде:

std::string vstring (const char * format, ...) {
  std::string result;

  va_list args;
  va_start(args, format);

  char buffer[1024];
  vsnprintf(buffer, sizeof(buffer), format, args);
  result = std::string(buffer);

  va_end(args);
  return result;
}

Это кажется ошибочным и хакерским.Есть ли способ для std::string быть сконструирован или работать на va_list напрямую?

ПРИМЕЧАНИЕ: Основная проблема, с которой я столкнулся при решении вышенужно угадать, сколько памяти мне нужно.Я не хочу тратить слишком много или не иметь достаточно.В идеале мне бы хотелось, чтобы непрозрачное выделение в стиле std::string просто работало.

ПРИМЕЧАНИЕ: Мне нужно решение, которое не требует поддержки сторонних библиотек.

Ответы [ 2 ]

3 голосов
/ 29 марта 2019

vsnprintf() может вычислить необходимый размер буфера без фактического вывода в буфер, так что вам обычно вообще не нужен отдельный char[], вы можете просто рассчитать размер, выделить std::string к этому размеру, а затем использовать собственный внутренний буфер std::string для вывода, например:

std::string vstring (const char * format, ...)
{
  std::string result;
  va_list args, args_copy;

  va_start(args, format);
  va_copy(args_copy, args);

  int len = vsnprintf(nullptr, 0, format, args);
  if (len < 0) {
    va_end(args_copy);
    va_end(args);
    throw std::runtime_error("vsnprintf error");
  }

  if (len > 0) {
    result.resize(len);
    // note: &result[0] is *guaranteed* only in C++11 and later
    // to point to a buffer of contiguous memory with room for a
    // null-terminator, but this "works" in earlier versions
    // in *most* common implementations as well...
    vsnprintf(&result[0], len+1, format, args_copy); // or result.data() in C++17 and later...
  }

  va_end(args_copy);
  va_end(args);

  return result;
}

Хотя до C ++ 11 использование отдельного буфера было бы более«правильный» (т. е. портативный) и более безопасный выбор, например:

std::string vstring (const char * format, ...)
{
  std::string result;
  va_list args, args_copy;

  va_start(args, format);
  va_copy(args_copy, args);

  int len = vsnprintf(nullptr, 0, format, args);
  if (len < 0) {
    va_end(args_copy);
    va_end(args);
    throw std::runtime_error("vsnprintf error");
  }

  if (len > 0) {
    std::vector<char> buffer(len+1);
    vsnprintf(&buffer[0], buffer.size(), format, args_copy);
    result = std::string(&buffer[0], len);
  }

  va_end(args_copy);
  va_end(args);

  return result;
}
2 голосов
/ 29 марта 2019

Вы можете использовать тот факт, что snprintf может использоваться с nullptr буфером и размером 0, чтобы получить результирующий размер буфера и записать сообщение в сам std::string.

Обратите внимание, что va_copy следует использовать, если вы хотите повторно использовать va_list.

std::string vformat(const char *format, va_list args)
{
    va_list copy;
    va_copy(copy, args);
    int len = std::vsnprintf(nullptr, 0, format, copy);
    va_end(copy);

    if (len >= 0) {
        std::string s(std::size_t(len) + 1, '\0');
        std::vsnprintf(&s[0], s.size(), format, args);
        s.resize(len);
        return s;
    }

    const auto err = errno;
    const auto ec = std::error_code(err, std::generic_category());
    throw std::system_error(ec);
}

std::string format(const char *format, ...)
{
    va_list args;
    va_start(args, format);
    const auto s = vformat(format, args);
    va_end(args);
    return s;
}
...