Использование функций C для манипулирования std :: string - PullRequest
0 голосов
/ 09 января 2019

Иногда вам нужно заполнить std::string символами, созданными функцией C. Типичный пример такой:

constexpr static BUFFERSIZE{256};
char buffer[BUFFERSIZE];
snprint (buffer, BUFFERSIZE, formatstring, value1, value2);
return std::string(buffer);

Обратите внимание, как нам сначала нужно заполнить локальный буфер, а затем скопировать его в std::string.

Пример становится более сложным, если рассчитывается максимальный размер буфера и не обязательно то, что вы хотите сохранить в стеке. Например:

constexpr static BUFFERSIZE{256};
if (calculatedBufferSize>BUFFERSIZE)
   {
   auto ptr = std::make_unique<char[]>(calculatedBufferSize);
   snprint (ptr.get(), calculatedBufferSize, formatstring, value1, value2);
   return std::string(ptr.get());
   }
else
   {
   char buffer[BUFFERSIZE];
   snprint (buffer, BUFFERSIZE, formatstring, value1, value2);
   return std::string(buffer);
   }

Это делает код еще более сложным, и если рассчитанныйBufferSize больше, чем мы хотим в стеке, мы по существу делаем следующее:

  • выделить память (make_unique)
  • заполнить память желаемым результатом
  • выделить память (std :: string)
  • копировать память в строку
  • освободить память

Так как C ++ 17 std::string имеет неконстантный метод data(), подразумевающий, что это способ манипулировать строками. Так что кажется заманчивым сделать это:

std::string result;
result.resize(calculatedBufferSize);
snprint (result.data(), calculatedBufferSize, formatstring, value1, value2);
result.resize(strlen(result.c_str()));
return result;

Мои эксперименты показывают, что последнее изменение размера необходимо, чтобы убедиться, что длина строки сообщается правильно. std::string::length() не ищет нуль-терминатор, он просто возвращает размер (как и std::vector).

Обратите внимание, что у нас намного меньше выделения и копирования:

  • выделить память (изменить размер строки)
  • заполнить память желаемым результатом

Честно говоря, хотя он кажется гораздо более эффективным, он также выглядит очень «нестандартным» для меня. Кто-нибудь может указать, разрешено ли это поведение стандартом C ++ 17? Или есть другой способ сделать этот вид манипуляций более эффективным способом?

Пожалуйста, не обращайтесь к вопросу Управление std :: string , так как этот вопрос о гораздо более грязной логике (даже при использовании memset). Также не отвечайте, что я должен использовать потоки C ++ (std::string_stream, эффективно?, Честно?). Иногда у вас просто есть эффективная логика в C, которую вы хотите использовать повторно.

Ответы [ 2 ]

0 голосов
/ 09 января 2019

Общий подход выглядит хорошо для меня. Я бы сделал пару изменений.

  1. Захватить возвращаемое значение snprinf.
  2. Используйте его для проверки ошибок и избежания звонка на strlen.

std::string result;
result.resize(calculatedBufferSize);
int n = snprint (result.data(), calculatedBufferSize, formatstring, value1, value2);

if ( n < 0 )
{
   // Problem. Deal with the error.
}

result.resize(n);
return result;
0 голосов
/ 09 января 2019

Изменение содержимого, на которое указывает data(), возможно, если вы не установите значение data() + size() для чего-либо, кроме нулевого символа. От [string.accessors] :

charT* data() noexcept;

Возвращает: Указатель p такой, что p + i == addressof(operator[](i)) для каждого i в [0, size()].

Сложность: Постоянное время.

Примечания: Программа не должна изменять значение, хранящееся в p + size(), на любое значение, отличное от charT(); в противном случае поведение не определено.


Выражение result.resize(strlen(result.c_str())); выглядит немного странно. std::snprintf возвращает количество написанных символов; использование этого значения для изменения размера строки будет более подходящим. Кроме того, выглядит немного аккуратно, чтобы создать строку с правильным размером вместо создания пустой, размер которой сразу же изменяется:

std::string result(maxlen, '\0');
result.resize(std::max(0, std::snprintf(result.data(), maxlen, fmt, value1, value2)));
return result;
...