Эффективный и современный С++. 42 рекомендации по использованию С++11 и С++14 - [123]

Шрифт
Интервал

. Эта операция бесплатна.

Благодаря использованию std::forward lvalue-аргументы >std::string копируются в >Widget::names, в то время как rvalue-apryмeнты >std::string перемещаются. Итоговая стоимость для аргументов >std::string такая же, как и при перегрузке: одно копирование для lvalue, одно перемещение для rvalue.

В разделе 5.3 поясняется, что если вызывающая функция передает аргумент, отличный от >std: :string, он будет передан в конструктор >std::string, и это может привести к нулевому количеству копирований и перемещений >std::string. Таким образом, функции, получающие универсальные ссылки, оказываются уникально эффективными. Однако это не влияет на выполняемый нами анализ, так что для простоты мы будем предполагать, что вызывающая функция всегда передает аргументы >std::string.

• Передача по значению. Независимо от передачи lvalue или rvalue должен быть сконструирован параметр >newName. Если передано lvalue, это стоит одно копирование, если передано rvalue — одно перемещение. В теле функции >newName безусловно перемещается в >Widget::names. Итоговая стоимость, таким образом, равна одному копированию и одному перемещению для lvalue и двум перемещениям для rvalue. По сравнению с подходом с передачей ссылки мы получаем одно лишнее перемещение как для lvalue, так и для rvalue.

Взглянем еще раз на название раздела:

Рассмотрите передачу по значению для копируемых параметров, которые легко перемещаются и всегда копируются.

Оно сформулировано таким образом не без причины. Точнее, не без четырех причин.

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

2. Рассмотрите передачу по значению только для копируемых параметров. Параметры, не соответствующие этому условию, должны иметь типы, являющиеся только перемещаемыми, поскольку если они не копируемые, а функция всегда делает копию, такая копия должна создаваться с помощью перемещающего конструктора[30]. Вспомним, что преимущества передачи по значению перед перегрузкой заключаются в том, что при передаче по значению достаточно написать только одну функцию. Но для только перемещаемых типов нет необходимости предоставлять перегрузку для lvalue, поскольку копирование lvalue влечет вызов копирующего конструктора, который у таких типов отсутствует. Это означает, что требуется поддержка только аргументов, являющихся rvalue, и в таком случае решение на основе “перегрузки” требует только одну перегрузку, принимающую rvalue-ссылку.

Рассмотрим класс с данными-членом >std::unique_ptr и функцию установки для него. Тип >std::unique_ptr является только перемещаемым типом, так что подход с использованием “перегрузки” состоит из единственной функции.

>class Widget {

>public:

> …

> void setPtr(std::unique_ptr&& ptr)

> { p = std::move(ptr); }

>private:

> std::unique_ptr p;

>};

Вызывающая функция может использовать ее следующим образом:

>Widget w;

>…

>w.setPtr(std::make_unique("Modern С++"));

Здесь rvalue >std::unique_ptr, возвращаемое из >std::make_unique (см. раздел 4.4), передается по ссылке в >setPtr, где оно перемещается в данные-член >p. Общая стоимость составляет одно перемещение. Если бы >setPtr принимала параметры по значению,

>class Widget {

>public:

> …

> void setPtr(std::unique_ptr ptr)

> { p = std::move(ptr); }

> …

>};

то тот же вызов создавал бы параметр >ptr перемещением, а затем >ptr был бы перемещен в данные-член >p. Общая стоимость составила бы два перемещения — в два раза больше, чем при подходе с “перегрузкой”.

3. Передачу по значению стоит рассматривать только для параметров с недорогим перемещением. Когда перемещение дешевое, стоимость дополнительного перемещения может быть приемлемой, но если это не так, то излишнее перемещение становится аналогичным излишнему копированию, а важность устранения излишнего копирования и была основной причиной появления правила С++98 о нежелательности применения передачи по значению.

4. Вы должны рассматривать передачу по значению только для параметров, которые всегда копируются. Чтобы увидеть, почему это важно, предположим, что перед копированием параметра в контейнер >names функция >addName проверяет, не слишком ли короткое (или длинное) имя передано. Если это так, запрос на добавление имени игнорируется. Реализация с передачей по значению может быть написана следующим образом:

>class Widget {

>public:

> void addName(std::string newName) {

>  if ((newName.length() >= minLen) &&

>     (newName.length() <= maxLen)) {

>   names.push_back(std::move(newName));

>  }

> }

> …

>private:

> std::vector names;

>};

Эта функция берет на себя создание и уничтожение >newName, даже если в >names ничего не добавляется. Это цена, которую подход с передачей по ссылке платить не должен.


Еще от автора Скотт Мейерс
Эффективное использование STL

В этой книге известный автор Скотт Мейерс раскрывает секреты настоящих мастеров, позволяющие добиться максимальной эффективности при работе с библиотекой STL.Во многих книгах описываются возможности STL, но только в этой рассказано о том, как работать с этой библиотекой. Каждый из 50 советов книги подкреплен анализом и убедительными примерами, поэтому читатель не только узнает, как решать ту или иную задачу, но и когда следует выбирать то или иное решение — и почему именно такое.


Как функции, не являющиеся методами, улучшают инкапсуляцию

Когда приходится инкапсулировать, то иногда лучше меньше, чем большеЯ начну со следующего утверждения: Если вы пишете функцию, которая может быть выполнена или как метод класса, или быть внешней по отношению к классу, Вы должны предпочесть ее реализацию без использования метода. Такое решение увеличивает инкапсуляцию класса. Когда Вы думаете об использовании инкапсуляции, Вы должны думать том, чтобы не использовать методы.Удивлены? Читайте дальше.


Рекомендуем почитать
Pro Git

Разработчику часто требуется много сторонних инструментов, чтобы создавать и поддерживать проект. Система Git — один из таких инструментов и используется для контроля промежуточных версий вашего приложения, позволяя вам исправлять ошибки, откатывать к старой версии, разрабатывать проект в команде и сливать его потом. В книге вы узнаете об основах работы с Git: установка, ключевые команды, gitHub и многое другое.В книге рассматриваются следующие темы:основы Git;ветвление в Git;Git на сервере;распределённый Git;GitHub;инструменты Git;настройка Git;Git и другие системы контроля версий.


Java 7

Рассмотрено все необходимое для разработки, компиляции, отладки и запуска приложений Java. Изложены практические приемы использования как традиционных, так и новейших конструкций объектно-ориентированного языка Java, графической библиотеки классов Swing, расширенной библиотеки Java 2D, работа со звуком, печать, способы русификации программ. Приведено полное описание нововведений Java SE 7: двоичная запись чисел, строковые варианты разветвлений, "ромбовидный оператор", NIO2, новые средства многопоточности и др.


MFC и OpenGL

В книге рассказывается история главного героя, который сталкивается с различными проблемами и препятствиями на протяжении всего своего путешествия. По пути он встречает множество второстепенных персонажей, которые играют важные роли в истории. Благодаря опыту главного героя книга исследует такие темы, как любовь, потеря, надежда и стойкость. По мере того, как главный герой преодолевает свои трудности, он усваивает ценные уроки жизни и растет как личность.


Симуляция частичной специализации

В книге рассказывается история главного героя, который сталкивается с различными проблемами и препятствиями на протяжении всего своего путешествия. По пути он встречает множество второстепенных персонажей, которые играют важные роли в истории. Благодаря опыту главного героя книга исследует такие темы, как любовь, потеря, надежда и стойкость. По мере того, как главный герой преодолевает свои трудности, он усваивает ценные уроки жизни и растет как личность.


Обработка событий в С++

В книге рассказывается история главного героя, который сталкивается с различными проблемами и препятствиями на протяжении всего своего путешествия. По пути он встречает множество второстепенных персонажей, которые играют важные роли в истории. Благодаря опыту главного героя книга исследует такие темы, как любовь, потеря, надежда и стойкость. По мере того, как главный герой преодолевает свои трудности, он усваивает ценные уроки жизни и растет как личность.


Питон — модули, пакеты, классы, экземпляры

Python - объектно-ориентированный язык сверхвысокого уровня. Python, в отличии от Java, не требует исключительно объектной ориентированности, но классы в Python так просто изучить и так удобно использовать, что даже новые и неискушенные пользователи быстро переходят на ОО-подход.