Конструктор принимает std :: string_view vs std :: string и перемещает - PullRequest
1 голос
/ 16 июня 2020

Предположим, у меня есть класс, который имеет член std::string, и я хочу принять значение этого члена в одном из его конструкторов.

Один из подходов - взять параметр типа std::string, а затем использовать std::move:

Foo(std::string str) : _str(std::move(str)) {}

Насколько я понимаю, перемещение строки просто копирует ее внутренние указатели, что означает в основном это бесплатно, поэтому передача const char* будет столь же эффективной, как передача const std::string&.

Однако в C ++ 17 мы получили std::string_view с обещанием дешевых копий. Таким образом, приведенное выше может быть записано как:

Foo(std::string_view str) : _str(str.begin(), str.end()) {}

Нет необходимости в перемещениях или построении временных std::string, но я думаю, что на самом деле он фактически просто делает то же самое, что и раньше.

Так что-то мне здесь не хватает? Или это просто вопрос стиля, используете ли вы std::string_view или std::string с перемещением?

Ответы [ 2 ]

2 голосов
/ 16 июня 2020

Рассмотрим некоторый сценарий ios:

Foo(std::string s) : str_(std::move(s)) {}
string s1;

Foo("abc");           // A - construct from string literal
Foo (s1);             // B - construct from existing string
Foo (string("def"));  // C - construct from temporary string
  • В случае (A) компилятор создает временную строку, передает ее конструктору Foo, который перемещается из нее .
  • В случае (B) компилятор создает копию s1 и передает ее конструктору Foo, который перемещается из копии.
  • В случае (C) компилятор передает временную строку конструктору Foo, который перемещается из него.

Если вместо этого, мы имеем:

Foo(std::string_view sv) : str_(sv.begin() sv.end()) {}
string s1;

Foo("abc");           // A - construct from string literal
Foo (s1);             // B - construct from existing string
Foo (string("def"));  // C - construct from temporary string
  • В случае (A) компилятор создает string_view (вызывает strlen) и передает его. Символьные данные копируются в str_
  • . В случае (B) компилятор создает string_view из s1.data() и s1.size() и передает их. Символьные данные копируются в str_
  • . В случае (C) создает строковое представление из временной строки и передает это. Символьные данные копируются в str_

Ни один из подходов не является лучшим во всех случаях. Первый подход отлично работает для (A) и (C), и подходит для (B)

Второй подход отлично работает для (A) и (B), но не так хорош для (C).

2 голосов
/ 16 июня 2020

Нет необходимости в перемещениях или создании временных std :: string, но я думаю, что на самом деле он фактически делает то же самое, что и раньше.

Это полностью зависит от того, что есть у пользователя, когда они называют ваш конструктор. Итак, давайте рассмотрим ваш случай 1 (std::string) и случай 2 (std::string_view). В обоих случаях конечный результат - std::string. Кроме того, этот анализ будет игнорировать оптимизацию небольших строк.

Итак, вот некоторые варианты, которые мы можем рассмотреть:

  • У пользователя есть строковый литерал.

    • в случае 1 будет копия в параметр std::string с последующим перемещением в std::string в классе.

    • В случае 2, будет копия указателя и размера, а затем копия символов в std::string в классе.

    В обоих случаях , длина литерала должна быть вычислена через char_traits::length в какой-то момент. Если пользователь использует UDL ("some_string"s или "some_string"sv) для вычисления аргумента перед его передачей, тогда вы можете избежать вызова char_traits::length среды выполнения.

    Итак, в этом случае они в основном то же самое.

  • У пользователя есть std::string lvalue, значение которого они хотят сохранить.

    • В случае 1 будет копию в параметр std::string с последующим перемещением в член std::string.

    • В случае 2 будет копия указателя и размера в std::string_view параметр, за которым следует копия символов в std::string в классе

    В обоих случаях длина не вычисляется, поскольку std::string знает ее длину. Опять же, в этом случае они одинаковы.

  • У пользователя есть значение std::string, которое он хочет переместить в объект. Таким образом, это либо prvalue, либо явное std::move.

    • В случае 1 будет построение перемещения параметра, за которым последует построение перемещения элемента.

    • В случае 2 будет копия указателя и размера, за которой следует копия символов в член std::string.

    Видите разницу? В случае 1 никакие символы никогда не копируются; есть только ходы. Это потому, что то, что есть у пользователя, и то, что вашему классу нужно , идентичны. Таким образом, вы получаете наиболее эффективную передачу.

    В случае 2 символы должны быть скопированы, потому что параметр string_view не знает, что пользователь не хочет хранить строку. Следовательно, конструктор вызываемого члена string тоже не работает.

Когда вы используете посредник для передачи, где исходный и целевой типы совпадают, тогда вы можете ввести неэффективность. Если у пользователя есть тип, который вы действительно собираетесь использовать, то для вашего интерфейса лучше использовать express напрямую этого типа с точки зрения производительности. Если вы используете посредник просмотра, то информация и намерения между вызывающим и вызываемым могут быть потеряны.

string_view - это тип лингва-франка; он в первую очередь предназначен, когда вы хотите использовать массив символов, не заставляя пользователя использовать определенный строковый тип c. Для случая использования, когда вы намереваетесь сохранить эти символы за пределами вызова функции, тип lingua-franca является неоптимальным, потому что only , что вы можете сделать, чтобы сохранить их, - это скопировать их в свою собственную строку .

Если только не важно сохранить std::string (или любой другой тип строки, который вы используете) вне вашего интерфейса, или если пользователь не может напрямую передать тип, в котором вы храните символы (вы может хранить массив, например), вы должны использовать его как тип параметра.

Но, конечно, это все территория микрооптимизации. Если только этот класс не используется целиком, разница незначительна.

...