Я слышал недавний разговор Херба Саттера, который предположил, что причины для прохождения std::vector
и std::string
по const &
большей части исчезли. Он предположил, что написание функции, такой как следующее, теперь предпочтительнее:
std::string do_something ( std::string inval )
{
std::string return_val;
// ... do stuff ...
return return_val;
}
Я понимаю, что это return_val
будет r-значение в точке, которую возвращает функция, и поэтому может быть возвращено с использованием семантики перемещения, которая очень дешева. Тем не менее, inval
он все еще намного больше, чем размер ссылки (которая обычно реализуется как указатель). Это связано с тем, что a std::string
имеет различные компоненты, включая указатель в кучу и элемент char[]
для оптимизации коротких строк. Поэтому мне кажется, что передача по ссылке все еще хорошая идея.
Кто-нибудь может объяснить, почему Херб мог это сказать?
Ответы:
Причина, по которой Херб сказал то, что он сказал, заключается в таких случаях.
Допустим, у меня есть функция,
A
которая вызывает функциюB
, которая вызывает функциюC
. ИA
передает строку черезB
и вC
.A
не знает или не заботитсяC
; всеA
знают о томB
. То естьC
деталь реализацииB
.Допустим, что A определяется следующим образом:
Если B и C принимают строку
const&
, то это выглядит примерно так:Все хорошо. Вы просто передаете указатели, без копирования, без движения, все счастливы.
C
принимает,const&
потому что он не хранит строку. Он просто использует это.Теперь я хочу сделать одно простое изменение:
C
нужно где-то хранить строку.Здравствуйте, конструктор копирования и потенциальное выделение памяти (игнорируйте Short String Optimization (SSO) ). Предполагается, что семантика перемещения в C ++ 11 позволяет удалить ненужное копирование, верно? И
A
проходит временный; нет никаких причин, почемуC
нужно копировать данные. Он должен просто скрыться с тем, что ему было дано.За исключением того, что не может. Потому что требуется
const&
.Если я изменю
C
свой параметр на значение, это просто приведетB
к копированию в этот параметр; Я ничего не получаю.Поэтому, если бы я только что передал
str
по значению все функции, полагаясь наstd::move
перемешивание данных, у нас не было бы этой проблемы. Если кто-то хочет удержать его, он может. Если нет, то ладно.Это дороже? Да; перемещение в значение дороже, чем использование ссылок. Это дешевле, чем копия? Не для небольших строк с SSO. Стоит ли это делать?
Это зависит от вашего варианта использования. Насколько вы ненавидите распределение памяти?
источник
moved
от B до C в случае значения? Если BB(std::string b)
и C,C(std::string c)
то либо мы должны позвонитьC(std::move(b))
в B, либоb
остаться неизменными (таким образом, «не двигаясь от») до выходаB
. (Возможно, оптимизирующий компилятор переместит строку в соответствии с правилом «как будто», еслиb
не используется после вызова, но я не думаю, что есть надежная гарантия.) То же самое верно для копииstr
tom_str
. Даже если параметр функции был инициализирован с помощью значения r, он является значением l внутри функции иstd::move
должен выходить из этого значения.Нет . Многие люди принимают этот совет (в том числе Дейва Абрахамса) за пределами области, к которой он применяется, и упрощают ее для применения ко всем
std::string
параметрам. Всегда передачаstd::string
по значению не является «наилучшей практикой» для любых и всех произвольных параметров и приложений, поскольку оптимизация этих доклады / статьи сосредоточены на применении только к ограниченному кругу дел .Если вы возвращаете значение, изменяете параметр или принимаете значение, то передача по значению может сэкономить дорогое копирование и обеспечить синтаксическое удобство.
Как всегда, передача по константной ссылке значительно экономит копирование, когда вам не нужна копия .
Теперь к конкретному примеру:
Если размер стека является проблемой (и предполагается, что он не встроен / не оптимизирован),
return_val
+inval
>return_val
- IOW, пиковое использование стека может быть уменьшено путем передачи значения здесь (примечание: упрощение ABI). Между тем, передача по константной ссылке может отключить оптимизацию. Основная причина здесь не в том, чтобы избежать роста стека, а в том, чтобы гарантировать, что оптимизация может быть выполнена там, где это применимо .Дни прохождения по константной ссылке не прошли - правила просто сложнее, чем когда-то. Если производительность важна, вам будет разумно рассмотреть, как вы передаете эти типы, основываясь на деталях, которые вы используете в своих реализациях.
источник
Это сильно зависит от реализации компилятора.
Однако это также зависит от того, что вы используете.
Рассмотрим следующие функции:
Эти функции реализованы в отдельном модуле компиляции, чтобы избежать встраивания. Затем:
1. Если вы передадите литерал этим двум функциям, вы не увидите большой разницы в производительности. В обоих случаях необходимо создать строковый объект.
2. Если вы передадите другой объект std :: string,
foo2
он превзойдетfoo1
, потомуfoo1
что сделает глубокое копирование.На моем ПК, используя g ++ 4.6.1, я получил следующие результаты:
источник
Краткий ответ: НЕТ! Длинный ответ:
const ref&
.(
const ref&
очевидно, что он должен оставаться в пределах видимости, в то время как выполняющая его функция)value
, не копируйтеconst ref&
внутри тела вашей функции.На cpp-next.com была публикация под названием «Хотите скорость, передавайте по значению!» , TL; DR:
ПЕРЕВОД ^
Не копировать аргументы вашей функции --- означает: если вы планируете изменить значение аргумента, скопировав его во внутреннюю переменную, просто используйте вместо него аргумент значения .
Итак, не делайте этого :
сделать это :
Когда вам нужно изменить значение аргумента в теле функции.
Вам просто нужно знать, как вы планируете использовать аргумент в теле функции. Только для чтения или НЕ ... и если он остается в рамках.
источник
const ref&
внутреннюю переменную, чтобы изменить ее. Если вам нужно изменить его ... сделайте параметр значением. Это довольно ясно для моего не говорящего по-английски я.const ref&
. # 2 Если мне нужно написать или я знаю, что это выходит за рамки ... Я использую значение. # 3 Если мне нужно изменить исходное значение, я прохожу мимоref&
. # 4 Я использую,pointers *
если аргумент не является обязательным, поэтому я могуnullptr
это сделать.Если вам на самом деле не нужна копия, ее все равно целесообразно взять
const &
. Например:Если вы измените это, чтобы получить строку по значению, то вы в конечном итоге переместите или скопируете параметр, и в этом нет необходимости. Мало того, что копирование / перемещение, вероятно, дороже, но это также представляет новый потенциальный сбой; копирование / перемещение может вызвать исключение (например, распределение во время копирования может завершиться ошибкой), тогда как получение ссылки на существующее значение не может.
Если вам действительно нужна копия , то передача и возвращение по значению обычно (всегда?) Наилучший вариант. На самом деле я бы вообще не беспокоился об этом в C ++ 03, если только вы не обнаружите, что лишние копии действительно вызывают проблемы с производительностью. Копирование Elision кажется довольно надежным на современных компиляторах. Я думаю, что скептицизм и настойчивость людей в том, что вам нужно проверить таблицу поддержки компилятором RVO, в настоящее время в основном устарели.
Короче говоря, C ++ 11 на самом деле ничего не меняет в этом отношении, за исключением людей, которые не доверяют разрешению копирования.
источник
noexcept
, но конструкторы копирования, очевидно, нет.Почти.
В C ++ 17 мы имеем
basic_string_view<?>
, что сводит нас к одному узкому варианту использованияstd::string const&
параметров.Существование семантики перемещения исключило один вариант использования для
std::string const&
- если вы планируете сохранить параметр, выборstd::string
значения по является более оптимальным, поскольку вы можетеmove
выходить из параметра.Если кто-то вызвал вашу функцию с необработанным C,
"string"
это означает, чтоstd::string
выделен только один буфер, а не два в этомstd::string const&
случае.Однако, если вы не собираетесь делать копию, взятие по-
std::string const&
прежнему полезно в C ++ 14.С
std::string_view
тех пор, пока вы не передадите указанную строку в API, который ожидает'\0'
символьные буферы, определенные в стиле C , вы можете более эффективно получатьstd::string
аналогичную функциональность без риска какого-либо выделения. Необработанная строка C может быть даже преобразована вstd::string_view
без выделения или копирования символов.В этот момент, использование используется,
std::string const&
когда вы не копируете данные оптом и собираетесь передать их API-интерфейсу в стиле C, который ожидает нулевой завершенный буфер, и вам нужны строковые функции более высокого уровня, которыеstd::string
предоставляют. На практике это редкий набор требований.источник
std::string
чтобы доминировать, вам нужны не только некоторые из этих требований, но и все они. Я признаю, что любой из них или даже два из них являются общими. Может быть, вам обычно нужны все 3 (например, вы делаете какой-то анализ строкового аргумента, чтобы выбрать, на какой C API вы собираетесь передавать его оптом?)std::string_view
- как вы отмечаете , «необработанную строку C можно даже превратить в std :: string_view без какого-либо выделения или копирования символов», что стоит запомнить тем, кто использует C ++ в контексте такое использование API, действительно.std::string
это не простые старые данные (POD) , и их необработанный размер - не самая актуальная вещь. Например, если вы передадите строку, длина которой превышает длину SSO и выделена в куче, я ожидаю, что конструктор копирования не будет копировать хранилище SSO.Причина, по которой это рекомендуется, заключается в том, что
inval
он составлен из выражения аргумента и, следовательно, всегда перемещается или копируется соответствующим образом - при этом не происходит потери производительности, при условии, что вам необходимо владение аргументом. Если вы этого не сделаете,const
ссылка может быть лучшим способом пойти.источник
std::string<>
из стека выделяется 32 байта, 16 из которых необходимо инициализировать. Сравните это только с 8 байтами, выделенными и инициализированными для ссылки: это вдвое больше объема работы ЦП и занимает в четыре раза больше места в кеше, которое не будет доступно для других данных.Я скопировал / вставил ответ на этот вопрос здесь и изменил имена и орфографию, чтобы соответствовать этому вопросу.
Вот код для измерения того, что спрашивают:
Для меня это выводы:
В таблице ниже приведены мои результаты (используется clang -std = c ++ 11). Первое число - это число конструкций копирования, а второе - количество конструкций перемещения:
Решение для передачи по значению требует только одной перегрузки, но требует дополнительной конструкции перемещения при передаче значений l и xvalue. Это может или не может быть приемлемым для любой конкретной ситуации. Оба решения имеют свои преимущества и недостатки.
источник
Херб Саттер до сих пор записывается вместе с Бьярном Страуструпом в
const std::string&
качестве параметра параметра; видеть https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Rf-in .Здесь есть подводный камень, не упомянутый ни в одном другом ответе: если вы передаете строковый литерал
const std::string&
параметру, он передаст ссылку на временную строку, созданную на лету для хранения символов литерала. Если вы затем сохраните эту ссылку, она будет недействительной после освобождения временной строки. Чтобы быть в безопасности, вы должны сохранить копию , а не ссылку. Проблема связана с тем, что строковые литералы являютсяconst char[N]
типами, требующими продвиженияstd::string
.Приведенный ниже код иллюстрирует ловушку и обходной путь, наряду с небольшим параметром эффективности - перегрузкой с помощью
const char*
метода, как описано в разделе Есть ли способ передачи строкового литерала в качестве ссылки в C ++ .(Примечание: Sutter & Stroustroup советуют, если вы сохраняете копию строки, также предоставьте перегруженную функцию с параметром && и std :: move () it.)
ВЫВОД:
источник
T&&
это не всегда универсальная ссылка; на самом деле,std::string&&
это всегда будет ссылка rvalue, а не универсальная ссылка, потому что никакого вывода типа не производится . Таким образом, совет Stroustroup & Sutter не противоречит Мейерсу.T&&
является ли какая-либо конкретная ссылка выведенной или не выведенной ссылкой; возможно, было бы яснее, если бы они вводили другой символ для универсальных ссылок (например&&&
, в виде комбинации&
и&&
), но это, вероятно, выглядело бы глупо.ИМО, используя ссылку C ++ для
std::string
- это быстрая и короткая локальная оптимизация, в то время как передача по значению может быть (или нет) лучшей глобальной оптимизацией.Таким образом, ответ: это зависит от обстоятельств:
const std::string &
.std::string
поведению конструктора копирования.источник
См. «Херб Саттер» «Назад к основам! Основы современного стиля C ++» . Среди других тем он рассматривает рекомендации по передаче параметров, которые были даны в прошлом, а также новые идеи, которые приходят с C ++ 11, и, в частности, рассматривает Идея передачи строк по значению.
Тесты показывают, что передача
std::string
s по значению в случаях, когда функция все равно скопирует его, может быть значительно медленнее!Это потому, что вы заставляете его всегда делать полную копию (и затем перемещаться на место), в то время как
const&
версия обновит старую строку, которая может повторно использовать уже выделенный буфер.См. Его слайд 27: для «установленных» функций опция 1 такая же, как и всегда. Вариант 2 добавляет перегрузку для ссылки на значение, но это дает комбинаторный взрыв, если имеется несколько параметров.
Только для параметров «приемника», где должна быть создана строка (без изменения ее существующего значения), трюк передачи по значению действителен. То есть конструкторы в которых параметр непосредственно инициализирует член соответствующего типа.
Если вы хотите увидеть, насколько глубоко вы можете беспокоиться об этом, посмотрите презентацию Николая Йосуттиса и удачи с этим ( «Отлично - Готово!» N раз после обнаружения ошибки в предыдущей версии. Вы когда-нибудь были?)
Это также обобщено как ⧺F.15 в Стандартных руководящих принципах.
источник
Как отмечает @ JDługosz в комментариях, Херб дает другой совет в другом (позже?) Выступлении, смотрите примерно здесь: https://youtu.be/xnqTKD8uD64?t=54m50s .
Его совет сводится только к использованию параметров-значений для функции,
f
которая принимает так называемые аргументы-приемники, при условии, что вы будете перемещать конструкцию из этих аргументов-приемников.Этот общий подход добавляет только издержки конструктора перемещения для аргументов lvalue и rvalue по сравнению с оптимальной реализацией
f
специально настроенных аргументов lvalue и rvalue соответственно. Чтобы понять, почему это так, предположим, чтоf
принимает параметр-значение, гдеT
есть некоторый тип копирования и перемещения:Вызов
f
с аргументом lvalue приведет к тому, что конструктор копирования вызывается для конструированияx
, а конструктор перемещения вызывается для конструированияy
. С другой стороны, вызовf
с аргументом rvalue вызовет конструктор перемещения для вызова и вызовет конструкторx
другого перемещенияy
.В общем, оптимальная реализация
f
аргументов для lvalue выглядит следующим образом:В этом случае для конструирования вызывается только один конструктор копирования
y
. Оптимальная реализацияf
аргументов для rvalue, опять же, в общем, выглядит следующим образом:В этом случае для конструирования вызывается только один конструктор перемещения
y
.Таким образом, разумный компромисс состоит в том, чтобы принять параметр-значение и иметь один дополнительный вызов конструктора перемещения для аргументов lvalue или rvalue относительно оптимальной реализации, что также является советом, данным в выступлении Херба.
Как отметил @ JDługosz в комментариях, передача по значению имеет смысл только для функций, которые будут создавать некоторый объект из аргумента приемника. Когда у вас есть функция,
f
которая копирует свой аргумент, подход с передачей по значению будет иметь больше издержек, чем общий подход с передачей по константной ссылке. Подход с передачей по значению для функции,f
которая сохраняет копию своего параметра, будет иметь вид:В этом случае есть конструкция копирования и назначение перемещения для аргумента lvalue, а также конструкция перемещения и назначение перемещения для аргумента rvalue. Наиболее оптимальный случай для аргумента lvalue:
Это сводится только к назначению, которое потенциально намного дешевле, чем конструктор копирования, плюс назначение перемещения, необходимое для подхода с передачей по значению. Причина этого заключается в том, что назначение может повторно использовать существующую выделенную память в
y
и, следовательно, предотвращать (де) выделение, тогда как конструктор копирования обычно выделяет память.Для аргумента rvalue наиболее оптимальная реализация, в
f
которой сохраняется копия, имеет вид:Таким образом, в этом случае только назначение перемещения. Передача r-значения в версию,
f
которая принимает константную ссылку, стоит только назначение, а не перемещение. Итак, условно говоря, версияf
использования константной ссылки в этом случае в качестве общей реализации является предпочтительным.Таким образом, в общем, для наиболее оптимальной реализации вам потребуется перегрузить или выполнить какую-то идеальную пересылку, как показано в докладе. Недостатком является комбинаторный рост количества требуемых перегрузок, в зависимости от количества параметров, на
f
случай, если вы решите перегрузить категорию значения аргумента. У идеальной пересылки есть недостаток, которыйf
превращается в функцию шаблона, которая не позволяет сделать его виртуальным, и приводит к значительно более сложному коду, если вы хотите сделать его на 100% правильным (подробности см. В докладе).источник
Проблема в том, что «const» - это не гранулярный классификатор. Что обычно подразумевается под «const string ref», так это «не изменяйте эту строку», а не «не изменяйте счетчик ссылок». В C ++ просто нет возможности сказать, какие члены являются «const». Они либо все, либо ни один из них.
Чтобы обойти эту проблему языка, STL мог бы разрешить "C ()" в вашем примере в любом случае сделать семантическую копию перемещения и покорно игнорировать "const" в отношении счетчика ссылок (изменяемых). Пока это было точно указано, это было бы хорошо.
Так как STL этого не делает, у меня есть версия строки, которая const_casts <> удаляет счетчик ссылок (нет способа задним числом сделать что-то изменяемое в иерархии классов), и - о чудо - вы можете свободно передавать cmstring как константные ссылки, и делайте их копии в глубоких функциях, весь день, без утечек или проблем.
Поскольку C ++ здесь не предлагает «гранулярность const производного класса», написание хорошей спецификации и создание блестящего нового объекта «const movable string» (cmstring) - лучшее решение, которое я видел.
источник