Коли я повинен використовувати string_view в інтерфейсі?


16

Я використовую внутрішню бібліотеку, яка була створена для імітації запропонованої бібліотеки C ++ , і десь за останні кілька років я бачу, що її інтерфейс змінився від використання std::stringна string_view.

Тож я достойно змінюю свій код, щоб відповідати новому інтерфейсу. На жаль, те, що мені потрібно передати, - це параметр std :: string, а також те, що є поверненим значенням std :: string. Тому мій код змінився з чогось такого:

void one_time_setup(const std::string & p1, int p2) {
   api_class api;
   api.setup (p1, special_number_to_string(p2));
}

до

void one_time_setup(const std::string & p1, int p2) {
   api_class api;
   const std::string p2_storage(special_number_to_string(p2));
   api.setup (string_view(&p1[0], p1.size()), string_view(&p2_storage[0], p2_storage.size()));
}

Я дійсно не бачу, що ця зміна придбала мене як клієнта API, окрім більшого коду (можливо, накручується). Виклик API менш безпечний (через те, що API більше не володіє сховищем для його параметрів), ймовірно, врятував мою програму 0 роботи (завдяки компіляторам оптимізацій переміщення зараз можна зробити), і навіть якщо це збереже роботу, це було б лише пара виділень, які не будуть і ніколи не будуть зроблені після запуску або десь у великому циклі. Не для цього API.

Однак, схоже, цей підхід дотримується порад, які я бачу в інших місцях, наприклад, у цій відповіді :

На відміну від C ++ 17 вам слід уникати передачі const std :: string & на користь std :: string_view:

Я вважаю цю пораду дивовижною, оскільки, здається, пропонується повсюдна заміна відносно безпечного об'єкта на менш безпечний (в основному прославлений покажчик і довжина), насамперед для оптимізації.

Отже, коли слід використовувати string_view, а коли не слід?


1
вам ніколи не доведеться викликати std::string_viewконструктор безпосередньо, вам слід просто передати рядки методу std::string_viewпрямого і він автоматично перетвориться.
Mgetz

@Mgetz - Хммм. Я ще не використовую повноцінний компілятор C ++ 17, тому, мабуть, це є більшою частиною проблеми. Однак, зразок коду тут, здавалося, вказує на його необхідність, принаймні, при оголошенні.
ТЕД

4
Дивіться мою відповідь, оператор перетворення знаходиться в <string>заголовку і відбувається автоматично. Цей код є обманним і неправильним.
Mgetz

1
"з менш безпечним", як фрагмент менш безпечний, ніж посилання рядка?
CodesInChaos

3
@ TED Абонент може так само легко звільнити рядок, на яку вказує ваша посилання, так як вони можуть звільнити пам'ять, на яку вказує фрагмент.
CodesInChaos

Відповіді:


18
  1. Чи потрібно функціоналу, що приймає значення, взяти право власності на рядок? Якщо так, використовуйте std::string(non-const, non-ref). Цей параметр дає вам можливість явно переміщати значення, а також якщо ви знаєте, що він ніколи не буде використаний знову в контексті виклику.
  2. Чи функціонал просто читає рядок? Якщо так, використовуйте std::string_view(const, non-ref), це тому, що string_viewможна легко std::stringі char*без проблем видавати копію. Це повинно замінити всі const std::string&параметри.

Зрештою, вам ніколи не потрібно дзвонити std::string_viewконструктору таким, яким ви є. std::stringмає оператор перетворення, який обробляє перетворення автоматично.


Просто для уточнення одного моменту, я думаю, що такий оператор перетворення також би піклувався про найгірші проблеми, пов’язані з життям, переконуючись, що значення рядка RHS залишається навколо на всю тривалість дзвінка?
ТЕД

3
@TED, якщо ви просто читаєте значення, це значення перевищить виклик. Якщо ви приймаєте право власності, то це повинно перевершити виклик. Тому я розглядав обидва випадки. Оператор перетворення просто займається std::string_viewспрощенням використання. Якщо розробник неправильно використовує його у власній ситуації, це помилка програмування. std::string_viewсуворо не володіє.
Mgetz

Чому const, non-ref? Параметр const залежить від конкретного використання, але в цілому є розумним як non-const. І ви пропустили 3. Може приймати скибочки
v.oddou

У чому проблема проходження const std::string_view &замість const std::string &?
ceztko

@ceztko це зовсім непотрібно і додає додатковий непрямий характер для доступу до даних.
Mgetz

15

A std::string_viewприносить деякі переваги від a const char*до C ++: на відміну від std::stringstring_view

  • не володіє пам'яттю,
  • не виділяє пам'ять,
  • може вказувати на існуючий рядок при деякому зміщенні, і
  • має один менший рівень непрямості вказівника, ніж a std::string&.

Це означає, що string_view часто може уникати копій, не маючи справи з необробленими покажчиками.

У сучасному коді std::string_viewслід замінити майже всі const std::string&параметри використання функцій. Це має бути зміна, сумісна з джерелами, оскільки std::stringоголошує оператора перетворення в std::string_view.

Тільки тому, що подання рядка не допомагає у вашому конкретному випадку використання, де вам все одно потрібно створити рядок, ще не означає, що це взагалі погана ідея. Стандартна бібліотека C ++, як правило, оптимізується для загальності, а не для зручності. Аргумент "менш безпечний" не відповідає, оскільки не слід створювати перегляд рядків самостійно.


2
Великим недоліком std::string_viewє відсутність c_str()методу, внаслідок чого виникають непотрібні проміжні std::stringоб'єкти, які потрібно побудувати та виділити. Особливо це проблема в API низького рівня.
Маттіас

1
@Matthias Це хороший момент, але я не вважаю його величезним недоліком. Перегляд рядків дозволяє вказувати на існуючий рядок при деякому зміщенні. Ця підрядка не може бути скасована нулем, для цього вам потрібна копія. Перегляд рядка не забороняє вам робити копію. Це дозволяє безліч завдань обробки рядків, які можна виконати за допомогою ітераторів. Але ви маєте рацію, що API, яким потрібна рядок C, не отримуватиме користі від переглядів. Тоді посилання на рядок може бути більш підходящим.
амон

@Matthias, чи не відповідає string_view :: data () c_str ()?
Ейліан

3
@Jeevaka C-рядок повинен бути нульовим завершенням, але дані подання рядка зазвичай не нульові, оскільки вони вказують на існуючий рядок. Наприклад, якщо у нас є рядок abcdef\0і рядок, який вказує на cdeпідрядку, після символу немає нульового символу e- в початковій рядку є є f. У стандартних також зазначає: «дані () може повертати покажчик на буфер , який не є нулем. Тому, як правило, помилково передавати дані () функції, яка бере лише const charT * і очікує нульову строку. "
amon

1
@kayleeFrye_onDeck Дані вже є покажчиком. Проблема з рядками C полягає не в отриманні вказівника char, а в тому, що рядок C має бути припинено з нуля. Дивіться приклад в моєму попередньому коментарі.
амон

8

Я вважаю цю пораду дивовижною, оскільки, здається, пропонується повсюдна заміна відносно безпечного об'єкта на менш безпечний (в основному прославлений покажчик і довжина), насамперед для оптимізації.

Я думаю, це трохи нерозуміє мету цього. Незважаючи на те, що це "оптимізація", ви дійсно повинні вважати це тим, що відмовляєте себе від необхідності використання std::string.

Користувачі C ++ створили десятки різних класів струн. Класи рядків з фіксованою довжиною, оптимізовані для SSO класи, розмір буфера - параметр шаблону, рядкові класи, які зберігають хеш-значення, яке використовується для їх порівняння тощо. Деякі люди навіть використовують рядки на основі COW. Якщо є одна річ, яку люблять робити програмісти на C ++, це писати строкові класи.

І це ігнорує рядки, які створені та належать бібліотекам C. Голі char*s, можливо, з розміром якогось.

Отже, якщо ви пишете якусь бібліотеку, а ви берете файл const std::string&, користувач тепер повинен взяти будь-який рядок, який він використовував, і скопіювати його в std::string. Може, десятки разів.

Якщо ви хочете отримати доступ до std::stringінтерфейсу, орієнтованого на рядок, навіщо вам копіювати рядок? Ось така трата.

Принципові причини не приймати а string_view за параметр такі:

  1. Якщо ваша кінцева мета - передати рядок до інтерфейсу, який приймає рядок, що закінчується NUL ( fopenтощо).std::stringгарантовано припинено NUL; string_viewні. І дуже легко підкреслити подання, щоб зробити його не закінченим NUL; підрядка a std::stringскопіює підрядку в діапазон, що закінчується NUL.

    Я написав спеціальний тип стилю string_view, що закінчується NUL, саме для цього сценарію. Ви можете робити більшість операцій, але не ті, що порушують його статус, що припиняється NUL (наприклад, обрізка з кінця).

  2. Життєві питання. Якщо вам дійсно потрібно скопіювати те std::stringабо інше, щоб масив символів пережив виклик функції, найкраще вказати це наперед, взявши a const std::string &. Або просто std::stringяк параметр значення. Таким чином, якщо вони вже мають такий рядок, ви можете негайно заявити право власності на нього, і абонент може перейти до рядка, якщо їм не потрібно зберігати його копію.


Це правда? Єдиний стандартний рядковий клас, про який я знав у C ++ до цього, був std :: string. Існує деяка підтримка використання char * 's як "рядків" для зворотної сумісності з C, але мені майже ніколи не потрібно цим користуватися. Звичайно, існує багато визначених користувачем класів для майже будь-якого, що ви можете собі уявити, і рядки, ймовірно, включаються до цього, але мені майже ніколи їх не потрібно використовувати.
ТЕД

@ TED: Тільки тому, що ви "майже ніколи не повинні їх використовувати", не означає, що інші люди їх не використовують звичайно. string_viewце тип lingua franca, який може працювати з чим завгодно.
Ніколь Болас

3
@ TED: Тому я сказав "C ++ як середовище програмування", на відміну від "C ++ як мови / бібліотеки".
Нікол Болас

2
@ TED: " Тож я можу однаково сказати:" C ++, як середовище програмування, має тисячі класів контейнерів "? " І це так. Але я можу написати алгоритми, які працюють з ітераторами, і будь-які класи контейнерів, які слідують за цією парадигмою, працюватимуть з ними. Навпаки, "алгоритми", які можуть приймати будь-який суміжний масив символів, було набагато складніше написати. З string_view, це легко.
Ніколь Болас

1
@ TED: Масиви символів - це особливий випадок. Вони надзвичайно поширені, і різні контейнери суміжних символів відрізняються лише тим, як вони керують своєю пам'яттю, а не тим, як ви повторюєте дані. Отже, мати єдиний тип діапазону lingua franca, який може охоплювати всі такі випадки без використання шаблону, має сенс. Узагальнення поза цим - провінція діапазону ТС і шаблони.
Нікол Болас
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.