Як ефективно отримати `string_view` для підрядка` std :: string`


78

Використовуючи http://en.cppreference.com/w/cpp/string/basic_string_view як посилання, я не бачу способу зробити це більш елегантно:

std::string s = "hello world!";
std::string_view v = s;
v = v.substr(6, 5); // "world"

Гірше того, що наївний підхід є підводним каменем і залишає vзвисаюче посилання на тимчасове:

std::string s = "hello world!";
std::string_view v(s.substr(6, 5)); // OOPS!

Здається, я пам’ятаю щось на зразок того, що може бути доповненням до стандартної бібліотеки для повернення підрядка як подання:

auto v(s.substr_view(6, 5));

Я можу придумати такі обхідні шляхи:

std::string_view(s).substr(6, 5);
std::string_view(s.data()+6, 5);
// or even "worse":
std::string_view(s).remove_prefix(6).remove_suffix(1);

Чесно кажучи, я не думаю, що жодне з них є дуже приємним. Зараз найкраще, що я можу придумати, - це використовувати псевдоніми, щоб просто зробити речі менш багатослівними.

using sv = std::string_view;
sv(s).substr(6, 5);

4
"Я не думаю, що жодне з них є дуже приємним" Що не так із першим? Мені здається цілком зрозумілим. Редагувати: BTW поєднання двох методів, які мають чітке значення окремо ( string_view(s).substr(...)), здається приємнішим, ніж одна функція, яка робить дві речі одночасно ( .substr_view(...)), навіть якщо вона існувала.
Arthur Tacca,

2
@sehe: Ви пропонуєте std::string::substrповернути a std::string_view?
geza

1
@geza Я напевно пам'ятаю доповнення до std::basic_string<>інтерфейсу, які могли б додати операцію, як subtr_view, справді. Я згадав це у питанні. Я сподівався, що хтось відповість і скаже "Це пропозиція Nxxxx, яку відхилили / прийняли в C ++ zz або TSn"
sehe

1
@ArthurTacca, оскільки операція настільки поширена, я думаю, що має бути 1-крокова операція, можливо, також більш ефективна. І, звичайно, менше схильних до помилок: coliru.stacked-crooked.com/a/fd180519dc9b2f00 Безкоштовна функція тепер найкраще, що ми можемо зробити (за відсутності en.wikipedia.org/wiki/Uniform_Function_Call_Syntax )
sehe

4
@sehe, отже, результатом моєї тривоги в групі cpporg є те, що ніхто інший, здається, не думає, що string_view є проблемою. Відповідь, здається, "просто ніколи не повертайте string_view", додаючи string_view до довільного списку класів, які слід "просто знати", щоб не повертати. У цьому випадку std::string_view::substr()метод порушує свої власні правила, оскільки повертає string_view. Тож думаю, порада була б ніколи цього не робити. Використовуйте a std::string.
Richard Hodges

Відповіді:


47

Існує безкоштовний функціональний маршрут, але якщо ви також не надаєте перевантажень std::string, це зміїна яма.

#include <string>
#include <string_view>

std::string_view sub_string(
  std::string_view s, 
  std::size_t p, 
  std::size_t n = std::string_view::npos)
{
  return s.substr(p, n);
}

int main()
{
  using namespace std::literals;

  auto source = "foobar"s;

  // this is fine and elegant...
  auto bar = sub_string(source, 3);

  // but uh-oh...
  bar = sub_string("foobar"s, 3);
}

IMHO - весь дизайн string_view - це шоу жахів, яке поверне нас у світ сегментальних розладів та розлючених клієнтів.

оновлення:

Навіть додавання перевантажень std::string- це шоу жахів. Подивіться, чи зможете ви помітити витончену бомбу за часом ...

#include <string>
#include <string_view>

std::string_view sub_string(std::string_view s, 
  std::size_t p, 
  std::size_t n = std::string_view::npos)
{
  return s.substr(p, n);
}

std::string sub_string(std::string&& s, 
  std::size_t p, 
  std::size_t n = std::string::npos)
{
  return s.substr(p, n);
}

std::string sub_string(std::string const& s, 
  std::size_t p, 
  std::size_t n = std::string::npos)
{
  return s.substr(p, n);
}

int main()
{
  using namespace std::literals;

  auto source = "foobar"s;
  auto bar = sub_string(std::string_view(source), 3);

  // but uh-oh...
  bar = sub_string("foobar"s, 3);
}

Тут компілятор не знайшов про що попередити. Я впевнений, що перегляд коду теж не зробив би.

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

Оновлення

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

Консенсус-порада цієї групи полягає в тому, що std::string_viewніколи не слід повертати функцію, а це означає, що моя перша пропозиція вище - погана форма.

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

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

Таким чином, код такий:

auto s = get_something().get_suffix();

Безпечно, коли get_suffix()повертає a std::string(або за значенням, або за посиланням)

але є UB, якщо функція get_suffix () коли-небудь реконструйована, щоб повернути a std::string_view.

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

Тож відтепер, принаймні для мене, "майже завжди авто" доведеться стати, "майже завжди авто, крім випадків, коли це рядки".


3
@sehe оновлений ще гіршим сценарієм - результатом добрих намірів, які пішли не так. Очікуйте побачити це незабаром у кодовій базі ...
Річард Ходжес

9
@sehe Я дивуюся, що комітет не бачив цього. Здається, колись вони оцінили поліпшення швидкодії існуючих алгоритмів порівняно з безпекою коду. Я боюся, що це серйозна помилка.
Річард Ходжес,

4
Хто каже, що вони не бачили, що це настане? Це, мабуть, були варіанти зважування. Я маю на увазі, string foo(); bool bar(string_view); auto check = bar(foo());безпечно і розумно хотіти дозволити.
sehe

6
@sehe звичайно я розумію обгрунтування. Це привабливо. Цього можна досягти безпечно, якби string_view не можна було скопіювати та не перемістити. Тоді ви не могли повернути один із функції, і все було б добре.
Річард Ходжес,

6
@geza Ваше питання дуже добре підтверджує моє занепокоєння. Друге присвоєння bar- це string_viewпобудований з string_viewпобудованого з тимчасового std::string, який зараз зруйнований.
Річард Ходжес,

14

Ви можете використовувати оператор перетворення зі std :: string на std :: string_view :

std::string s = "hello world!";
std::string_view v = std::string_view(s).substr(6, 5);

Цей підхід, очевидно, не є ефективним (тобто чи виконує він роботу, не виділяючи зайвих тимчасових буферів?), Тому коментар, що підтверджує твердження про ефективність, був би непоганим. Насправді, схоже, такий підхід дуже протипоказаний, оскільки vвказує на тимчасовий буфер, який негайно стає недійсним.
Джон Доггетт,

@JohnDoggett: Про що ти говориш? Чому повинен бути тимчасовий буфер?
MikeMB

Імхо, це набагато краще рішення, ніж прийняте.
MikeMB

.substr(6, 5)виділяє новий, std::stringале це тимчасово і відходить на ;, залишаючи vвказівку на буфер, який був очищений.
шапки

3

Ось як ви можете ефективно створити підрядок string_view.

#include <string>
inline std::string_view substr_view(const std::string& source, size_t offset = 0,
                std::string_view::size_type count = 
                std::numeric_limits<std::string_view::size_type>::max()) {
    if (offset < source.size()) 
        return std::string_view(source.data() + offset, 
                        std::min(source.size() - offset, count));
    return {};
}

#include <iostream>
int main(void) {
  std::cout << substr_view("abcd",3,11) << "\n";

  std::string s {"0123456789"};
  std::cout << substr_view(s,3,2) << "\n";

  // be cautious about lifetime, as illustrated at https://en.cppreference.com/w/cpp/string/basic_string_view
  std::string_view bad = substr_view("0123456789"s, 3, 2); // "bad" holds a dangling pointer
  std::cout << bad << "\n"; // possible access violation

  return 0;
}

Можливо, було б непогано виділити "погане використання", оскільки наведений приклад легко переросте в такий. Запропонував редагувати саме це.
Джон Доггетт,

Запобігання поганому випадку -> std :: string_view substr_view (std :: string const && тощо) = delete;
Mercury Dime
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.