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

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

передается функции >w.setName. Вызывающий код можно простить за предположение о том, что эта функция по отношению к >n является операцией чтения. Но поскольку >setName внутренне использует >std::move для безусловного приведения своего ссылочного параметра к rvalue, значение >n может быть перемещено в >w.name, и >n вернется из вызова >setName с неопределенным значением. Этот вид поведения может привести программиста к отчаянию — если не к прямому насилию.

Можно возразить, что >setName не должен был объявлять свой параметр как универсальную ссылку. Такие ссылки не могут быть константными (см. раздел 5.2), но >setName, безусловно, не должен изменять свой параметр. Вы могли бы указать, что если перегрузить >setName для константных значений lvalue и rvalue, то этой проблемы можно было бы избежать, например, таким образом:

>class Widget {

>public:

> void setName(const std::string& newName) // Устанавливается

> { name = newName; }                      // из const lvalue

> void setName(std::string&& newName)      // Устанавливается

> { name = std::move(newName); }           // из rvalue

>};

В данном случае это, безусловно, сработает, но у метода есть и недостатки. Во-первых, требуется вводить исходный текст большего размера (две функции вместо одного шаблона). Во-вторых, это может быть менее эффективным. Например, рассмотрим следующее применение >setName:

>w.setName("Adela Novak");

При наличии версии >setName, принимающей универсальную ссылку, функции >setName будет передан строковый литерал >"Adela Novak", в котором он будет передан оператору присваивания для >std::string внутри >w. Таким образом, член-данные >name объекта >w будет присвоен непосредственно из строкового литерала; никакого временного объекта >std::string создаваться не будет. Однако в случае перегруженных версий >setName будет создан временный объект >std::string, с которым будет связан параметр функции >setName, и этот временный объект >std::string будет перемещен в член-данные объекта >w. Таким образом, вызов >setName повлечет за собой выполнение одного конструктора >std::string (для создания временного объекта), одного перемещающего оператора присваивания >std::string (для перемещения >newName в >w.name) и одного деструктора >std::string (для уничтожения временного объекта). Это практически наверняка более дорогостоящая последовательность операций, чем вызов только одного оператора присваивания >std::string, принимающего указатель >const char*. Дополнительная стоимость может варьироваться от реализации к реализации, и стоит ли беспокоиться о ней, зависит от приложения и библиотеки; однако, скорее всего, в ряде случаев замена шаблона, получающего универсальную ссылку, парой функций, перегруженных для lvalue- и rvalue-ссылок, приведет к дополнительным затратам времени выполнения.

Однако наиболее серьезной проблемой с перегрузкой для lvalue и rvalue является не объем или идиоматичность исходного кода и не производительность времени выполнения. Это — плохая масштабируемость проекта. >Widget::setName принимает только один параметр, так что необходимы только две перегрузки. Но для функций, принимающих большее количество параметров, каждый из которых может быть как lvalue, так и rvalue, количество перегрузок растет в соответствии с показательной функцией: n параметров требуют 2 перегрузок. И это еще не самый худший случай. Некоторые функции (на самом деле — шаблоны функций) принимают неограниченное количество параметров, каждый из которых может быть как lvalue, так и rvalue. Типичными представителями таких функций являются >std::make_shared и, начиная с С++14, >std::make_unique (см. раздел 4.4). Попробуйте написать объявления их наиболее часто используемых перегрузок:

>template // Из стандарта C++11

>shared_ptr make_shared(Args&&... args);


>template // Из стандарта С++14

>unique_ptr make_unique(Args&&... args);

Для функций наподобие указанных перегрузка для lvalue и rvalue не является приемлемым вариантом: единственным выходом является применение универсальных ссылок. А внутри таких функций, уверяю вас, к универсальным ссылкам при их передаче другим функциям следует применять >std::forward. Вот то, что вы должны делать.

Ну хорошо, обычно должны. В конечном итоге. Но не обязательно изначально. В некоторых случаях вы захотите использовать привязку объекта к rvalue-ссылке или универсальной ссылке более одного раза в одной функции, и вы захотите гарантировать, что перемещения не будет, пока вы явно не укажете его выполнить. В этом случае вы захотите применить >std::move (для rvalue-ссылок) или >std::forward (для универсальных ссылок) только к последнему использованию ссылки, например:

>template       // text - универсальная

>void setSignText(T&& text) // ссылка

>{

> sign.setText(text);       // Используем text, но не

>                           // изменяем его

> auto now =                // Получение текущего времени

>  std::chrono::system_clock::now();

> signHistory.add(now,

>  std::forward(text)); // Условное приведение

>}                         // text к rvalue

Здесь мы хотим гарантировать, что значение


Еще от автора Скотт Мейерс
Эффективное использование 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 так просто изучить и так удобно использовать, что даже новые и неискушенные пользователи быстро переходят на ОО-подход.