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

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

5.4. Избегайте перегрузок для универсальных ссылок

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

>std::multiset names; // Глобальная структура данных


>void logAndAdd(const std::string& name) {

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

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

> log(now, "logAndAdd"); // Создание журнальной записи

> names.emplace(name);   // Добавление nаmе в глобальную

>}                       // структуру данных; emplace

>                        // см. в разделе 8.2

Этот код не является неразумным, но он не такой эффективный, каким мог бы быть. Рассмотрим три потенциальных вызова:

>std::string petName("Darla");

>logAndAdd(petName);                   // lvalue типа std::string

>logAndAdd(std::string("Persephone")); // rvalue типа std::string

>logAndAdd("Patty Dog");               // Строковый литерал

В первом вызове параметр паше функции >logAndAdd связывается с переменной >petName. Внутри >logAndAdd параметр name в конечном итоге передается в вызов >names.emplace. Поскольку >name является lvalue, он копируется в >names. Избежать этого копирования невозможно, так как >lvalue(petName) передается в функцию >logAndAdd.

Во втором вызове параметр >name связывается с rvalue (временный объект >std::string, явно созданный из строки >"Persephone"). Параметр >name сам по себе является lvalue, так что он копируется в >names, но мы отдаем себе отчет, что, в принципе, это значение может быть перемещено в >names. В этом вызове мы платим за копирование, но мы должны быть способны сделать то же с помощью перемещения.

В третьем вызове параметр name опять связывается с rvalue, но в этот раз со временным объектом >std::string, который неявно создается из >"Patty Dog". Как и во втором вызове, >name копируется в >names, но в этот раз аргумент, изначально переданный в >logAndAdd, был строковым литералом. Если бы строковый литерал непосредственно передавался в >emplace, в создании временного объекта >std::string не было бы необходимости вообще. Вместо этого функция >emplace использовала бы строковый литерал для создания объекта >std::string непосредственно в >std::multiset. Таким образом, в этом третьем вызове мы платим за копирование >std::string, при том что нет причин платить даже за перемещение, не говоря уже о копировании.

Неэффективность второго и третьего вызовов >logAndAdd можно устранить, переписав эту функцию так, чтобы она принимала универсальную ссылку (см. раздел 5.2) и, согласно разделу 5.3, передавала ее с помощью >std::forward функции >emplace. Результат говорит сам за себя:

>template

>void logAndAdd(T&& name) {

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

> log(now, "logAndAdd");

> names.emplace(std::forward(name));

>}


>std::string petName("Darla");         // Как и ранее


>logAndAdd(petName);                   // Как и ранее, копирова-

>                                      // ние lvalue в multiset


>logAndAdd(std::string("Persephone")); // Перемещение rvalue

>                                      // вместо копирования


>logAndAdd("Patty Dog");               // Создание std::string

>                                      // в multiset вместо

>                                      // копирования временного

>                                      // std::string

Ура, получена оптимальная эффективность!

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

>std::string nameFromIdx(int idx); // Возвращает имя,

>                                  // соответствующее idx

>void logAndAdd(int idx)           // Новая перегрузка

>{

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

> log(now, "logAndAdd");

> names.emplace(nameFromIdx(idx));

>}

Разрешение перегрузки работает, как и следовало ожидать:

>std::string petName("Darla");         // Как и ранее


>logAndAdd(petName);                   // Как и ранее, эти вызовы

>logAndAdd(std::string("Persephone")); // приводят к использова-

>logAndAdd("Patty Dog");               // нию перегрузки для T&&


>logAndAdd(22);                        // Вызов int-перегрузки

На самом деле разрешение работает, как ожидается, только если вы не ожидаете слишком многого. Предположим, клиент имеет переменную типа >short, хранящую индекс, и передает ее функции >logAndAdd:

>short nameIdx;

>…                   // Дает значение переменной nameIdx

>logAndAdd(nameIdx); // Ошибка!

Комментарий в последней строке, может быть, не слишком понятен, так что позвольте мне пояснить, что же здесь произошло.

Имеется две перегрузки >logAndAdd. Одна из них, принимающая универсальную ссылку, может вывести тип >T как >short, тем самым приводя к точному соответствию. Перегрузка с параметром >int может соответствовать аргументу


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