Skip to content

Latest commit

 

History

History
130 lines (102 loc) · 5.47 KB

string_view.md

File metadata and controls

130 lines (102 loc) · 5.47 KB

string_view — тот же const& только больнее

С++17 подарил нам тип std::string_view, призванный убить сразу двух зайцев:

  • Проблемы с перегрузками для функций, которые должны работать хорошо как с C, так и с C++ строками
  • А также программистов, периодически забывающих о правилах продления жизни временным объектам.

И так, проблема: функция хочет считать количество вхождений какого-то символа в строку:

int count_char(const std::string& s, char c) {
    ....
}

count_char("hello world", 'l'); // создастся временный объект std::string,
// выделится память, скопируется строка, а потом строка умрет и память
// деаллоцируется — плохо, много лишних операций

Так что нам нужна перегрузка для С-строк

int count_char(const char* s, char c) {
    // мы тут не знаем ничего про длину строки
    // она вообще null-териминированная?

    // Можем только написать код, наивно рассчитывающий, что его
    // будут вызывать правильно.
    ...
}

И будем либо дублировать код, немного адаптируя его под C-строки, либо сделаем функцию

int count_char_impl(const char* s, size_t len, char 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, как раз таки являющийся парой: указатель и размер. И убивает обе перегрузки:

int count_char(std::string_view s, char c) {
    ...
}

И все здорово, хорошо и замечательно, кроме одного но:

std::string_view по сути является ссылочным типом, как const&, и его можно конструировать из временных значений. Но, в отличие от просто const&, никакого продления жизни не будет. Вернее будет, но не там, где ожидается.

auto GetString = []() -> std::string { return "hello"; };
std::string_view sv = GetString();
std::cout << sv << "\n"; // dangling reference!

В этом примере мы, конечно, почти явно стреляем себе в голову. Можно сделать стрельбу менее явной:

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
    }
}

Ситуация такая же, как с ранее рассмотренным std::min. Только защититься от такой функции common_prefix, обернув ее в шаблон с помощью анализа rvalue/lvalue, намного сложнее: нам нужно разобрать случаи const char* и std::string для каждого аргумента — в общем, все то, от чего нас введение std::string_view «избавило».

Влететь в string_view можно еще изящнее:

struct Person {
    std::string name;

    std::string_view Initials() const {
        if (name.length() <= 2) {
            return name;
        }
        return name.substr(0, 2); // copy — dangling reference!
    }
};

Причем видно, что 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