mirror of
https://github.com/Nekrolm/ubbook.git
synced 2026-06-09 13:14:18 +03:00
131 lines
5.5 KiB
Markdown
131 lines
5.5 KiB
Markdown
# 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
|