2017-01-25 3 views
4

Глядя на куске кода, как это (добавлены комментарии):C++ конкатенация строки Оптимизация

std::string some_var; 
std::string some_func(); // both are defined, but definition is irrelevant 
... 
return "some text " + some_var + "c" + some_func(); // intentionally "c" not 'c' 

Мне было интересно, , в каких случаях operator + из std::string должен сделать копию (в смысле использования копии -конструкция/назначение, а не копирование внутреннего буфера, например, если применяется SSO), и что фактически копируется. Быстрый взгляд на cppreference был лишь частично полезным, так как он перечисляет 12 (!) Разных случаев. В частности, я прошу, чтобы подтвердить свое понимание страницы:

  • Case 1) делает копию LHS затем копирует РИТ до конца этой копии
  • В C++ 98 Случай 2) - 5) временная строка построена из аргумента char/const char*, что затем приводит к случаю 1)
  • В C++ 11 Случай 2) - 5) временная строка построена из аргумента char/const char*, что затем приводит к случаю 6) или 7)
  • В C++ 11 Случай 6) - 12) аргумент r-value будет мутирован с insert/append и, если a char/const char* аргумент было предоставлено не временное место из-за перегрузок на insert/append. Во всех случаях возвращается значение r для облегчения дальнейшей цепочки. Копии не копируются (кроме копии аргументов, которые должны быть добавлены/вставлены в месте вставки). Может потребоваться перемещение содержимого строки.

Цепочка, подобная приведенному выше примеру, должна приводить к: 2) -> 6) -> 11) -> 8), без каких-либо копий каких-либо lhs, а просто модификации буфера r -значение, полученное в результате первой операции (создание временной строки).

Поэтому это кажется столь же эффективным, как и operator +=, как только operator + использует по крайней мере аргумент r-value. Это правильно, и есть ли смысл использовать operator += по сравнению с operator + в C++ 11 и после этого, если оба аргумента не являются строками l-value?

Какие оптимизации может сделать компилятор дополнительно?

Изменить: уточнить цель вопроса. Исходная часть касается специфики только языка (выполнение непогашения); последний вопрос касается дополнительных оптимизаций.

+0

Исправлено, что я считаю опечаткой. Откат назад, если вы имели в виду 'some_fun()'. – Bathsheba

+0

на самом деле не была опечаткой, но я предполагаю, что ваша версия более понятна в контексте C++. – midor

+0

Поучительно компилировать с помощью 'g ++ -save-temps' и смотреть на результат сборки ассемблера на разных уровнях оптимизации. С '-O3' он вызывает' string :: reserve() 'once и' string :: append() 'четыре раза для вашей части кода. –

ответ

-1

Чтобы объединить более двух строк, быстрее зарезервировать достаточное пространство для новой строки и добавить к ней разные строки (меньше конструкторов копий, операций, временных переменных ...), чем использовать много раз операцию на двух строках (результат, в Concat Например, в параметре ул):

void concat (const std::list<std::string>& ls, std::string& str) { 
    size_t ns (0); 
    std::list<std::string>::const_iterator i (ls.begin()); 
    for (; i != ls.end(); i++) ns += i->size(); 
    str.reserve (ns); 
    str.erase (str.begin(), str.end()); 
    for (i = ls.begin(); i != ls.end(); i++) str.append (*i); 
} 
+3

Это не то, о чем попросил ОП. Он не просил оптимизировать метод, он спросил, что может сделать компилятор в этом коде, что довольно часто. – bolov

+0

Не лучше ли использовать 'str.reserve (ns)' вместо 'str.resize (ns)'? –

+0

@ G.Sliepen Это две разные операции. 'resize' создает элементы последовательности, которые необходимы в этом случае. – Potatoswatter

0

строка представляет собой довольно непрозрачный объект: она имеет внутренний буфер обугленного и управляет его так, как он хочет. Добавление одного символа в строку может закончиться назначением нового буфера, копией исходной части и копией добавленной части. Все зависит от того, достаточно ли выделенного буфера, чтобы принять добавленную часть.

цитата говорит:

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

Said иначе новое распределение, полная копия и открепление старого буфера ...

И когда вы говорите о эффективности и оптимизации, вы должны помнить о том, что компилятор не следовать так, как вы написали программу. Из-за правила as-if он может оптимизировать то, как он хочет, при условии соблюдения наблюдаемого поведения. стандарт C++ говорит: выполнение

1,9 Программа [intro.execution]
...
5 Соответствующая реализация выполнение хорошо сформированная программы будет производить то же наблюдаемое поведение в качестве одного из возможных выполнений соответствующего экземпляр абстрактной машины с той же программой и тот же ввод.

примечание объясняет даже, что:

реализация волен игнорировать какие-либо требования настоящего международного стандарта до тех пор, как результат, как если бы требование было послушался, насколько может быть определяется из наблюдаемого поведения программы .

Так что вполне вероятно, что a = a + b; и a += b; собраны в точно такой же код.

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

+0

Мне все равно о эффективности, больше о семантике, так что это правильно, хотя и не очень ново для меня, но упускает мой вопрос. Мне больше заботятся о том, какие версии функции вызываются, а затем о том, как быстро это заканчивается на данный момент. Тот факт, что перераспределение может происходить между ними, ортогонален вопросу imo, потому что вы не можете избежать этого, если не знаете размер вверх и зарезервируете для содержимого. – midor

+0

* "Таким образом, вполне вероятно, что a = a + b и a + = b; скомпилированы точно таким же кодом." * Вы действительно проверили это? Я очень сомневаюсь, что любой компилятор действительно может сделать эту оптимизацию (кроме, может быть, если 'a' является временным). – MikeMB

+0

'a = a + b', по моему мнению, потребуется как минимум дополнительное перемещение. Лучший случай (при условии, что это возможно), компилятор понимает, что он должен быть переназначен, и, следовательно, его значение больше не требуется, рассматривает его как rvalue, что приводит к случаю 6), что приводит к добавлению к r-значению. Однако полученное значение r по-прежнему назначается a, и я не вижу никакого способа применить rvo здесь, потому что append не возвращает новый объект. – midor

Смежные вопросы