Передача string_view по ссылке или значению

179
26 октября 2021, 04:20

Как правильно передавать std::string_view в функцию, если не надо изменять строку?

Так

void foo(std::string_view);

или так

void foo(const std::string_view&);

?

Answer 1

Вопрос по сути своей родственен массе аналогичных вопросов по поводу других "легковесных" нетривиальных типов. Как лучше передавать std::initializer_list? std::function? Итераторы контейнеров?

Я бы сказал, что в тех ситуациях, когда у вас есть выбор (то есть вам не нужна копия объекта внутри функции), этот выбор является вопросом персональных предпочтений. Эти объекты действительно легковесны и какие-либо потери производительности при "неоправданной" передаче по значению вы скорее всего не заметите.

Я лично для класс-типов по старинке придерживаюсь правила "если есть сомнения - передавай по константной ссылке", хотя во многих ситуациях в современном С++ это правило уже себя изжило и может быть даже вредным.

Answer 2

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 & - это лишнее. Не надо тратить время на написание этих букв.

READ ALSO
Как компилировать быстрее?

Как компилировать быстрее?

Есть Incredibuild, но она вроде платная?

84
Предупреждение о неинициализированном массиве

Предупреждение о неинициализированном массиве

Как убрать предупреждение о неинициализированном массиве в следующем коде, ведь все элементы инициализированы?

242
VS C++ точки останова с проверкой std::string

VS C++ точки останова с проверкой std::string

Интересует возможность использования в Visual Studio (2017) условных точек останова (conditional breakpoint) с проверкой переменной типа std::stringНиже упрощенный...

155