Я слышал, как несколько человек выражали беспокойство по поводу оператора "+" в std::string и различные обходные пути для ускорения конкатенации. Действительно ли это необходимо? Если да, то каков наилучший способ конкатенации строк в С++?
Эффективная конкатенация строк в С++
Ответ 1
Дополнительная работа, вероятно, не стоит того, если вам действительно не нужна эффективность.. Вероятно, вы будете иметь гораздо лучшую эффективность, просто используя оператор + =.
Теперь после этого отказа от ответственности я отвечу на ваш реальный вопрос...
Эффективность строкового класса STL зависит от реализации используемого STL.
Вы можете гарантировать эффективность и иметь более высокий контроль самостоятельно, выполнив конкатенацию вручную с помощью встроенных функций c.
Почему оператор + не эффективен:
Взгляните на этот интерфейс:
template <class charT, class traits, class Alloc>
basic_string<charT, traits, Alloc>
operator+(const basic_string<charT, traits, Alloc>& s1,
const basic_string<charT, traits, Alloc>& s2)
Вы можете видеть, что после каждого + возвращается новый объект. Это означает, что каждый раз используется новый буфер. Если вы делаете тонну дополнительных операций, это неэффективно.
Почему вы можете сделать его более эффективным:
- Вы гарантируете эффективность, а не доверяете делегату, чтобы сделать это эффективно для вас.
- класс std::string ничего не знает о максимальном размере вашей строки и о том, как часто вы будете конкатенировать с ней. У вас могут быть эти знания и вы можете делать что-то на основе этой информации. Это приведет к меньшему перераспределению.
- Вы будете управлять буферами вручную, чтобы быть уверенным, что вы не будете копировать всю строку в новые буферы, когда вы этого не хотите.
- Вы можете использовать стек для буферов вместо кучи, который намного эффективнее. Оператор
- string + создает новый строковый объект и возвращает его, используя новый буфер.
Соображения для реализации:
- Следите за длиной строки.
- Держите указатель в конце строки и в начале, или просто в начале, и используйте начало + длину как смещение, чтобы найти конец строки.
- Убедитесь, что буфер, в котором хранится ваша строка, достаточно велик, поэтому вам не нужно перераспределять данные.
- Используйте strcpy вместо strcat, поэтому вам не нужно перебирать длину строки, чтобы найти конец строки.
Структура канатной структуры:
Если вам нужны действительно быстрые конкатенации, подумайте об использовании структуры данных .
Ответ 2
Зарезервируйте свое конечное пространство раньше, затем используйте метод append с буфером. Например, предположим, что ваша конечная длина строки должна составлять 1 миллион символов:
std::string s;
s.reserve(1000000);
while (whatever)
{
s.append(buf,len);
}
Ответ 3
Я бы не стал беспокоиться об этом. Если вы сделаете это в цикле, строки всегда будут выделять память для минимизации перераспределения - просто используйте operator+=
в этом случае. И если вы сделаете это вручную, что-то вроде этого или дольше
a + " : " + c
Затем он создает временные файлы - даже если компилятор может устранить некоторые копии возвращаемого значения. Это происходит потому, что в последовательном названии operator+
он не знает, ссылается ли ссылочный параметр на именованный объект или на временный объект, возвращаемый с вызова operator+
. Я бы предпочел не беспокоиться об этом, прежде чем не профилировать в первую очередь. Но давайте возьмем пример, чтобы показать это. Сначала мы вводим круглые скобки, чтобы сделать привязку понятной. Я приводил аргументы непосредственно после объявления функции, которое использовалось для ясности. Ниже я покажу, каким получилось следующее выражение:
((a + " : ") + c)
calls string operator+(string const&, char const*)(a, " : ")
=> (tmp1 + c)
Теперь, в этом добавлении, tmp1
- это то, что было возвращено первым вызовом operator + с показанными аргументами. Мы предполагаем, что компилятор действительно умный и оптимизирует копию возвращаемого значения. Таким образом, мы получаем одну новую строку, содержащую конкатенацию a
и " : "
. Теперь это происходит:
(tmp1 + c)
calls string operator+(string const&, string const&)(tmp1, c)
=> tmp2 == <end result>
Сравните это со следующим:
std::string f = "hello";
(f + c)
calls string operator+(string const&, string const&)(f, c)
=> tmp1 == <end result>
Он использует ту же функцию для временной и для именованной строки! Поэтому компилятор должен скопировать аргумент в новую строку и добавить к нему и вернуть его из тела operator+
. Он не может взять память о временном и добавить к этому. Чем больше выражение, тем больше копий строк должно быть выполнено.
Далее Visual Studio и GCC будут поддерживать семантику перемещения С++ 1x (дополняющую семантику копирования) и ссылки rvalue в качестве экспериментального дополнения. Это позволяет выяснить, ссылается ли параметр на временный или нет. Это сделает такие дополнения удивительно быстрыми, так как все вышеперечисленное закончится одним "добавлением-конвейером" без копий.
Если это окажется узким местом, вы все равно можете
std::string(a).append(" : ").append(c) ...
Вызов append
добавляет аргумент к *this
, а затем возвращает ссылку на себя. Таким образом, копирование временных объектов не производится. Или, альтернативно, можно использовать operator+=
, но для исправления приоритета вам потребуются уродливые скобки.
Ответ 4
Для большинства приложений это не имеет значения. Просто напишите свой код, блаженно не осознавая, как работает оператор "+", и только берете дело в свои руки, если оно становится очевидным узким местом.
Ответ 5
В отличие от .NET System.Strings, С++ std:: strings являются изменяемыми и поэтому могут быть созданы с помощью простой конкатенации так же быстро, как и с помощью других методов.
Ответ 6
возможно, std:: stringstream вместо?
Но я согласен с тем, что вы, вероятно, должны просто сохранить его под контролем и понятным, а затем профиль, чтобы узнать, действительно ли у вас проблемы.
Ответ 7
В Imperfect С++ Мэтью Уилсон представляет динамический конкатенатор строк, который предварительно вычисляет длину окончательной строки, чтобы иметь только одно выделение перед конкатенацией всех частей. Мы также можем реализовать статический конкатенатор, играя с шаблонами выражений.
Такая идея реализована в реализации STLport std::string, которая не соответствует стандарту из-за этого точного взлома.
Ответ 8
std::string
operator+
выделяет новую строку и копирует две строки операндов каждый раз. повторяйте много раз, и он становится дорогим, O (n).
std::string
append
и operator+=
, с другой стороны, увеличивайте емкость на 50% каждый раз, когда строка должна расти. Это значительно сокращает количество распределений памяти и операций копирования O (log n).
Ответ 9
Для маленьких строк это не имеет значения. Если у вас есть большие строки, вам лучше их хранить, поскольку они находятся в векторе или в какой-то другой коллекции в качестве частей. И добавьте свой алгоритм для работы с таким набором данных, а не с одной большой строкой.
Я предпочитаю std:: ostringstream для сложной конкатенации.
Ответ 10
Как и в большинстве случаев, легче делать что-то, чем делать.
Если вы хотите выводить большие строки в графический интерфейс, может быть, что все, что вы выводите, может обрабатывать строки по частям лучше, чем как большая строка (например, конкатенация текста в текстовом редакторе - обычно они сохраняются линии как отдельные структуры).
Если вы хотите вывести файл, потоки данных, а не создание большой строки и вывод этого.
Мне никогда не приходилось требовать ускорения конкатенации, если я удалил ненужную конкатенацию из медленного кода.
Ответ 11
Самый простой массив символов, инкапсулированный в класс, который отслеживает размер массива и количество выделенных байтов.
Фокус в том, чтобы сделать только одно большое выделение при запуске.
в
https://github.com/pedro-vicente/table-string
Бенчмарки
Для Visual Studio 2015, сборка отладки x86, улучшение подсистемы над С++ std::string.
| API | Seconds
| ----------------------|----|
| SDS | 19 |
| std::string | 11 |
| std::string (reserve) | 9 |
| table_str_t | 1 |
Ответ 12
Вероятно, лучшая производительность, если предварительно выделить (зарезервировать) пространство в результирующей строке.
template<typename... Args>
std::string concat(Args const&... args)
{
size_t len = 0;
for (auto s : {args...}) len += strlen(s);
std::string result;
result.reserve(len); // <--- preallocate result
for (auto s : {args...}) result += s;
return result;
}
Использование:
std::string merged = concat("This ", "is ", "a ", "test!");