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

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

, которые получают итератор “подсказки”, а у >std::forward_list имеется >emplace_after, соответствующий его >insert_after.

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

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

>std::string queenOfDisco("Donna Summer");

оба приведенных далее вызова корректны, и оба приводят к одному и тому же результату:

>vs.push_back(queenOfDisco);    // Копирующее конструирование

>                               // queenOfDisco в конце vs

>vs.emplace_back(queenOfDisco); // То же самое

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

Потому что теоретически разницы между теорией и практикой нет, а практически — есть. При текущих реализациях стандартной библиотеки имеются ситуации, когда, как и ожидается, размещение превосходит вставку, но — увы! — есть ситуации, когда вставка работает быстрее. Такие ситуации непросто охарактеризовать, поскольку они зависят от типов передаваемых аргументов, используемых контейнеров, местоположения вставки или размещения в контейнере, безопасности исключений конструкторов типов, содержащихся в контейнере, и для контейнеров, в которых запрещены дубликаты (т.e. >std::set, >std::map, >std::unordered_set, >std::unordered_map), от того, содержится ли уже в контейнере вставляемое значение. А потому следует пользоваться обычным советом по повышению производительности: для определения того, какой метод работает быстрее, надо сравнивать их реальную производительность в конкретных условиях.

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

• Добавляемое значение конструируется в контейнере, а не присваивается. Пример, с которого начат данный раздел (добавление >std::string со значением >"xyzzy" в >std::vector vs), демонстрирует значение, добавляемое в конец вектора >vs — в место, где пока что нет никакого объекта. Таким образом, новое значение должно быть сконструировано в >std::vector. Если мы пересмотрим пример так, что новая строка >std::string будет направляться в местоположение, уже занятое объектом, это будет совсем другая история:

>std::vector vs;     // Как и ранее

>…                                // Добавление элементов в vs

>vs.emplace(vs.begin(), "xyzzy"); // Добавление "xyzzy" в начало vs

При таком коде только редкие реализации будут конструировать добавляемый объект >std::string в памяти, занятой >vs[0]. Большинство реализаций используют перемещающее присваивание в указанное место. Но перемещающее присваивание требует наличия перемещаемого объекта, а это означает, что необходимо создание временного объекта. Поскольку основное преимущество размещения над вставкой заключается в том, что не создаются и не уничтожаются временные объекты, при добавлении значения в контейнер с помощью присваивания преимущества размещения исчезают.

Увы, выполняется ли добавление значения в контейнер путем конструирования или перемещения, в общем случае зависит от реализации. И вновь на помощь может прийти эвристика.

Контейнеры на основе узлов почти всегда используют для добавления новых значений конструирование, а большинство стандартных контейнеров являются именно таковыми. Исключениями являются >std::vector, >std::deque и >std::string. (Контейнер >std::array также не является таковым, но он не поддерживает ни вставку, ни размещение, поэтому упоминать о нем здесь нет смысла.) В контейнерах, не основанных на узлах, можно рассчитывать на использование функцией >emplace_back для размещения нового значения конструирования вместо присваивания; то же самое можно сказать и о функции >emplace_front контейнера >std::deque.

• Типы передаваемых аргументов отличаются от типа, хранящегося в контейнере. И вновь, преимущество размещения по сравнению со вставкой в общем случае связано с тем фактом, что его интерфейс не требует создания и уничтожения временного объекта при передаче аргументов типа, отличного от типа, хранящегося в контейнере. Когда в контейнер >container добавляется объект типа >T, нет причин ожидать, что размещение окажется быстрее вставки, поскольку для удовлетворения интерфейсу вставки не требуется создание временного объекта.


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