# std::string_view 函數傳值與編譯器最佳化

# std::string_view 函數傳值與編譯器最佳化

在使用 `c++17` 的 `std::string_view` 時肯定會有這個疑問：

> 我們該用 `const string_view&` 還是 `string_view` ？

因為他本身就是 reference 的意思了，所以在函數傳值時，直接傳值比較符合語意。

可是考慮到效能：他又至少是一個 struct 包含一個指標和整數，是不是該傳 `const&`？但這就像傳一個 reference 的 reference？

這裡從編譯器最佳化給出一個看法：**傳值即可**。

主要是用 `gcc x86-64 -std=c++17 -O3` 的結果。
*MSVC 可以考慮使用 `/O2` ，也會有類似的結果。*


## 實驗

*這個實驗抽自[Three reasons to pass std::string_view by value](https://quuxplusone.github.io/blog/2021/11/09/pass-string-view-by-value/)，然後再加一個 struct 的測試。*


我們可以做個實驗 ([godbolt](https://godbolt.org/z/54dM8foWE))：

```cpp
#include <string_view>

void byvalue(std::string_view sv);
void byref(const std::string_view& sv);

void callbyvalue() {
    byvalue("hello");
}

void callbyref() {
    byref("hello");
}
```

```x86asm
.LC0:
  .string "hello"

callbyvalue():
  mov edi, 5
  mov esi, OFFSET FLAT:.LC0
  jmp byvalue(std::basic_string_view<char, std::char_traits<char> >)

callbyref():
  sub rsp, 24
  mov rdi, rsp
  mov QWORD PTR [rsp], 5
  mov QWORD PTR [rsp+8], OFFSET FLAT:.LC0
  call byref(std::basic_string_view<char, std::char_traits<char> > const&)
  add rsp, 24
  ret
```


兩個差異在 call by value 實際上並沒有真的建立一個 `std::string_view` instance。

他就只是像直接傳兩個參數到函數裡面： const char pointer 和 int 。

但是 call by reference 卻會要先建立一個東西再傳過去。

但上面的例子我們會有疑慮：這是不是只是因為傳 const char pointer 才會這樣？，

我們可以再用一個 struct 來實驗 ([godbolt](https://godbolt.org/z/joWK4TW3x))：

```cpp
struct Foo {
    int a;
    const char* p;
};

void bar(int, const char*);

void byvalue(Foo sv);
void byref(const Foo& sv);

void cbar() {
    bar(5, "hello");
}

void cbval() {
    byvalue({5, "hello"});
}

void cbref() {
    byref({5, "hello"});
}
```

結果

```x86asm
.LC0:
  .string "hello"

cbar():
  mov esi, OFFSET FLAT:.LC0
  mov edi, 5
  jmp bar(int, char const*)

cbval():
  mov edi, 5
  mov esi, OFFSET FLAT:.LC0
  jmp byvalue(Foo)

cbref():
  sub rsp, 24
  mov rdi, rsp
  mov DWORD PTR [rsp], 5
  mov QWORD PTR [rsp+8], OFFSET FLAT:.LC0
  call byref(Foo const&)
  add rsp, 24
  ret
```

可以發現，`cbar` 和 `cbval` 基本上是一樣的。他們的意思都是傳兩個參數。 `cbref` 就是建立一個 struct 然後傳進去。 

在 `-O3` 的情況下，`cbref` 竟然意外的比 `cbval` 多操作！

## 結論

既然編譯器讓這兩個選擇的效能差異並沒有特別的突出(甚至有時會更好)，那語意的選擇便更為重要：**傳值就好**。

## 參考資料

* [Three reasons to pass std::string_view by value](https://quuxplusone.github.io/blog/2021/11/09/pass-string-view-by-value/)
* [A footnote on “Three reasons to pass std::string_view by value”](https://quuxplusone.github.io/blog/2021/11/19/string-view-by-value-ps/)

