Files
ubbook/lifetime/string_view.md
T
2022-03-24 14:03:41 +03:00

131 lines
5.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# string_view — тот же const& только больнее
С++17 подарил нам тип `std::string_view`, призванный убить сразу двух зайцев:
- Проблемы с перегрузками для функций, которые должны работать хорошо как с C, так и с C++ строками
- А также программистов, периодически забывающих о правилах продления жизни временным объектам.
И так, проблема: функция хочет считать количество вхождений какого-то символа в строку:
```C++
int count_char(const std::string& s, char c) {
....
}
count_char("hello world", 'l'); // создастся временный объект std::string,
// выделится память, скопируется строка, а потом строка умрет и память
// деаллоцируется — плохо, много лишних операций
```
Так что нам нужна перегрузка для С-строк
```C++
int count_char(const char* s, char c) {
// мы тут не знаем ничего про длину строки
// она вообще null-териминированная?
// Можем только написать код, наивно рассчитывающий, что его
// будут вызывать правильно.
...
}
```
И будем либо дублировать код, немного адаптируя его под C-строки, либо сделаем функцию
```C++
int count_char_impl(const char* s, size_t len, char c) {
...
}
```
В которую поместим весь дублирующийся код и вызовем ее из перегрузок:
```C++
int count_char(const std::string& s, char c) {
return count_char_impl(s.data(), s.size(), c);
}
int count_char(const char* s, char c) {
return count_char_impl(s, strlen(s), c);
}
```
И тут на помощь приходит `string_view`, как раз таки являющийся парой: указатель и размер. И убивает обе перегрузки:
```C++
int count_char(std::string_view s, char c) {
...
}
```
И все здорово, хорошо и замечательно, кроме одного но:
`std::string_view` по сути является ссылочным типом, как `const&`, и его можно конструировать из временных значений. Но, в отличие от просто `const&`, никакого продления жизни не будет. Вернее будет, но не там, где [ожидается](https://godbolt.org/z/nxxrYb).
```C++
auto GetString = []() -> std::string { return "hello"; };
std::string_view sv = GetString();
std::cout << sv << "\n"; // dangling reference!
```
В этом примере мы, конечно, почти явно стреляем себе в голову. Можно сделать стрельбу [менее явной](https://godbolt.org/z/PPcarE):
```C++
std::string_view common_prefix(std::string_view a, std::string_view b) {
auto len = std::min(a.size(), b.size());
auto common_count = [&]{
for (size_t common_len = 0; common_len < len; ++common_len) {
if (a[common_len] != b[common_len]) {
return common_len;
}
}
return len;
}();
return a.substr(0, common_count);
}
int main() {
using namespace std::string_literals;
{
auto common = common_prefix("helloW",
"hello"s + "World111111111111111111111");
std::cout << common << "\n"; // ok
}
{
auto common = common_prefix("hello"s + "World111111111111111111111111",
"helloW");
std::cout << common << "\n"; // dangling ref
}
}
```
Ситуация такая же, как с [ранее рассмотренным](use_after_free_in_general.md) `std::min`.
Только защититься от такой функции `common_prefix`, обернув ее в шаблон с помощью анализа rvalue/lvalue,
намного сложнее: нам нужно разобрать случаи `const char*` и `std::string` для каждого аргумента — в общем, все то, от чего нас введение `std::string_view` «избавило».
Влететь в `string_view` можно еще изящнее:
```C++
struct Person {
std::string name;
std::string_view Initials() const {
if (name.length() <= 2) {
return name;
}
return name.substr(0, 2); // copy — dangling reference!
}
};
```
Причем [видно](https://godbolt.org/z/TPc4zq), что Clang хотя бы выдает предупреждение.
А gcc — нет.
Все потому что `std::string_view` настолько легендарный, что в clang сделали хоть какой-то lifetime checker сперва для него.
## Полезные ссылки
1. https://quuxplusone.github.io/blog/2018/03/27/string-view-is-a-borrow-type/
2. https://foonathan.net/2017/03/string_view-temporary/
3. https://www.learncpp.com/cpp-tutorial/6-6a-an-introduction-to-stdstring_view/
4. https://github.com/isocpp/CppCoreGuidelines/blob/master/docs/Lifetime.pdf