std::string и std::string_view в C++: разница, производительность и безопасные паттерны

std::string и std::string_view в C++: разница, производительность и безопасные паттерны

std::string и std::string_view в C++: разница, производительность и безопасные паттерны

Запрос «string_view в C++: когда использовать и чем он лучше std::string» — один из самых частых у начинающих. Разобраться важно: от правильного выбора типа зависит и производительность, и безопасность кода. В этой статье мы сравним std::string и std::string_view, разберёмся с владением, временем жизни, покажем практичные примеры и типичные ошибки.

Ключевая идея: владение против представления

  • std::string — владеет буфером (динамическая память), отвечает за выделение/освобождение, изменяемый.
  • std::string_view — невладеющее представление (указатель + длина). Ничего не копирует и не освобождает. Только «смотрит» на чужие данные.
  • Из этого следует правило: если функция только читает строку, удобнее принимать std::string_view. Если нужно хранить, модифицировать или продлить жизнь данных — используйте std::string.

    Создание и совместимость

    std::string_view легко строится из строковых литералов, std::string и C‑строк:

    #include <string>
    #include <string_view>
    
    void f(std::string_view sv);
    
    int main() {
        std::string s = "hello";
        const char* c = "world";
    
        f(s);          // из std::string
        f(c);          // из C-строки
        f("literal"); // из литерала
    }
    

    Обратное преобразование (view → string) требует копирования:

    std::string_view sv = "data";
    std::string owned = std::string(sv); // копия, владение теперь у owned
    

    Где string_view особенно полезен

    1) Входные параметры без копий

    #include <string_view>
    #include <cctype>
    
    // Подсчёт слов: разделитель — пробельный символ
    size_t count_words(std::string_view text) {
        size_t count = 0;
        bool in_word = false;
        for (unsigned char ch : text) {
            if (std::isspace(ch)) {
                in_word = false;
            } else if (!in_word) {
                in_word = true;
                ++count;
            }
        }
        return count;
    }
    

    Функция принимает и std::string, и литералы, и C-строки без лишних копий.

    2) Быстрый trim без аллокаций

    #include <string_view>
    #include <cctype>
    
    std::string_view trim(std::string_view sv) {
        auto is_space = [](unsigned char c){ return std::isspace(c); };
        while (!sv.empty() && is_space(sv.front())) sv.remove_prefix(1);
        while (!sv.empty() && is_space(sv.back()))  sv.remove_suffix(1);
        return sv; // всё ещё view на исходные данные
    }
    

    3) Лёгкий сплит по разделителю

    #include <string_view>
    #include <vector>
    
    std::vector<std::string_view> split(std::string_view sv, char delim) {
        std::vector<std::string_view> parts;
        size_t start = 0;
        while (true) {
            size_t pos = sv.find(delim, start);
            if (pos == std::string_view::npos) {
                parts.push_back(sv.substr(start));
                break;
            }
            parts.push_back(sv.substr(start, pos - start));
            start = pos + 1;
        }
        return parts; // вектора view; копий строк нет
    }
    

    Важно: полученные подстроки живут не дольше исходных данных, на которые они ссылаются.

    Опасные места и как их избежать

    1) Возврат view на временные данные — ошибка

    // ПЛОХО: возвращаем view на локальный std::string
    std::string_view bad() {
        std::string s = "temp";
        return s; // висячая ссылка (dangling)
    }
    

    Правильно: вернуть std::string (владеющий) или принять буфер снаружи.

    // Хорошо: возвращаем владение
    std::string good() {
        std::string s = "ok";
        return s; // RVO/NRVO, без лишней копии
    }
    

    2) Долгое хранение string_view

    std::string make()
    {
        std::string s = "hello";
        return s;
    }
    
    std::string_view sv;
    {
        std::string tmp = make();
        sv = tmp; // sv указывает внутрь tmp
    } // tmp разрушен — sv теперь висячий
    

    Решение: если нужно хранить — храните std::string или делайте копию:

    std::string owned;
    {
        std::string tmp = make();
        owned = tmp; // корректно
    }
    

    3) Реаллокиация ломает все view

    std::string s = "abc";
    std::string_view v = s; // указывает внутрь s
    s += "def";            // возможна реаллокация буфера
    // v может стать недействительным
    

    Вывод: не храните string_view на строку, которую планируется изменять (push_back, append, resize и т.п.).

    Производительность: когда реально быстрее

  • Создание/копирование std::string_view — O(1).
  • Подстроки через substr у view — O(1), без копирования данных.
  • Копирование std::string — O(N), выделение памяти, перенос байтов.
  • Поэтому в API для чтения данных используйте string_view. В местах, где нужны собственные данные или модификация — string.

    Интероп с C‑API: c_str() и data()

  • std::string::c_str() и data() в C++17 возвращают нуль-терминированный буфер (const char*).
  • std::string_view::data() — указатель на первый символ без гарантии завершающего нуля.
  • extern "C" void c_api(const char*);
    
    std::string s = "hello";
    c_api(s.c_str()); // ок
    
    std::string_view sv = "world";
    // c_api(sv.data()); // может не содержать '' в конце — небезопасно
    // Решение: скопировать
    std::string tmp(sv);
    c_api(tmp.c_str());
    

    Практика: парсинг ключ=значение без копий

    #include <string_view>
    #include <optional>
    #include <utility>
    
    std::optional<std::pair<std::string_view, std::string_view>>
    parse_kv(std::string_view sv) {
        auto pos = sv.find('=');
        if (pos == std::string_view::npos) return std::nullopt;
        auto key = sv.substr(0, pos);
        auto val = sv.substr(pos + 1);
        return std::make_pair(trim(key), trim(val));
    }
    

    Функция возвращает пары view; при необходимости сохранения значений — скопируйте их в std::string.

    Частые вопросы

    Можно ли модифицировать через string_view? Нет. Это read-only представление. Если нужно изменить — используйте string или span к изменяемому буферу.

    Как быть с Unicode? std::string — это просто байты. Операции size(), substr() работают по байтам, а не по кодовым точкам. Для Unicode учитывайте кодировку (UTF‑8) и используйте специализированные библиотеки.

    Рекомендации и паттерны

  • В публичном API для входных строк — std::string_view.
  • Не храните string_view дольше жизни источника; не используйте его на изменяемую строку.
  • Нужна долговременная копия — явный std::string.
  • Для C‑API используйте c_str() у std::string; у string_view при необходимости делайте копию.
  • Профилируйте: string_view ускоряет код, убирая копии. Но не жертвуйте безопасностью времени жизни.
  • Хотите системно прокачать основы C++ (типы, ссылки, строки, память, ООП) и практику на проектах? Загляните в пошаговый курс «C++ с Нуля до Гуру» — от синтаксиса к реальным задачам.

    Итоги

    std::string — для владения и модификации. std::string_view — для чтения без копий. Соблюдайте правила времени жизни и не храните view «на будущее». С этими принципами ваш код станет одновременно быстрее и надёжнее.

    Источник

    НЕТ КОММЕНТАРИЕВ

    Оставить комментарий