Как правильно передавать std::string_view
в функцию, если не надо изменять строку?
Так
void foo(std::string_view);
или так
void foo(const std::string_view&);
?
Вопрос по сути своей родственен массе аналогичных вопросов по поводу других "легковесных" нетривиальных типов. Как лучше передавать std::initializer_list
? std::function
? Итераторы контейнеров?
Я бы сказал, что в тех ситуациях, когда у вас есть выбор (то есть вам не нужна копия объекта внутри функции), этот выбор является вопросом персональных предпочтений. Эти объекты действительно легковесны и какие-либо потери производительности при "неоправданной" передаче по значению вы скорее всего не заметите.
Я лично для класс-типов по старинке придерживаюсь правила "если есть сомнения - передавай по константной ссылке", хотя во многих ситуациях в современном С++ это правило уже себя изжило и может быть даже вредным.
TL;DR: По значению.
Если у string_view
надо вызывать методы типа remove_prefix
/remove_suffix
, то её надо передавать по значению, т.к. иначе придется делать не-константную копию.
void foo(std::string_view);
void bar(std::string_view view) { | void bar(const std::string_view& view) {
if (view.starts_with("http:")) { | auto copy = view;
view.remove_prefix(5); | if (view.starts_with("http:")) {
} | copy.remove_prefix(5);
foo(view); | }
} | foo(view);
| }
В случае если функция будет заинлайнена, то конечно же не будет никакой разницы, т.к. компилятор уберет ссылку. Однако, в общем случае функция не будет заинлайнена, и тогда:
По значению быстрее потому что не будет лишней индирекции при доступе к содержимому string_view
.
Однако при передаче по значению компилятору может понадобиться сделать копию
void bar(std::string_view view) {
foo(view);
view.remove_prefix(5);
foo(view);
}
т.к. вызов функции foo
может испортить регистры с аргументами (rsi и rdi), то компилятор копирует view
в другие регистры (rbx и rbp).
push rbp
mov rbp, rdi
push rbx
mov rbx, rsi
sub rsp, 8
call foo(std::basic_string_view<char, std::char_traits<char> >)
add rsp, 8
lea rdi, [rbp-5]
lea rsi, [rbx+5]
pop rbx
pop rbp
jmp foo(std::basic_string_view<char, std::char_traits<char> >)
size_t n1, n2;
void some();
void bar(const std::string_view& view) {
n1 = view.size();
some();
n2 = view.size();
}
т.к. вызов some()
может поменять значение view
, то компилятор два раза возьмет size()
из view
(при этом он скопирует аргумент из rdi в rbx).
push rbx
mov rax, QWORD PTR [rdi]
mov rbx, rdi
mov QWORD PTR n1[rip], rax
call some()
mov rax, QWORD PTR [rbx]
pop rbx
mov QWORD PTR n2[rip], rax
ret
Для сравнения, передача по-значению:
push rbx
mov rbx, rdi
mov QWORD PTR n1[rip], rdi
call some()
mov QWORD PTR n2[rip], rbx
pop rbx
ret
Вывод: для string_view
, const &
- это лишнее. Не надо тратить время на написание этих букв.
Айфон мало держит заряд, разбираемся с проблемой вместе с AppLab
Как убрать предупреждение о неинициализированном массиве в следующем коде, ведь все элементы инициализированы?
Интересует возможность использования в Visual Studio (2017) условных точек останова (conditional breakpoint) с проверкой переменной типа std::stringНиже упрощенный...