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

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

• Маловероятно, что контейнер отвергает новое значение как дубликат. Это означает, что либо контейнер разрешает наличие дубликатов, либо большинство передаваемых значений уникальны. Это важно, поскольку для того, чтобы определить наличие дубликата, реализации размещения обычно создают узел с новым значением, а затем сравнивают его с имеющимися узлами контейнера. Если добавляемое значение в контейнере отсутствует, узел встраивается в контейнер. Однако, если такое значение уже есть в контейнере, размещение прерывается, а узел уничтожается, так что впустую расходуется стоимость создания и уничтожения объекта. Такие узлы для функций размещения создаются более часто, чем для функций вставки.

Приведенные вызовы, с которыми мы уже сталкивались в данном разделе, удовлетворяют всем перечисленным критериям. Они работают быстрее соответствующих вызовов >push_back.

>vs.emplace_back("xyzzy"); // Конструирует новое значение в конце

>                          // контейнера; тип аргумента отличен от

>                          // типа, хранимого в контейнере;

>                          // контейнер не отвергает дубликаты

>vs.emplace_back(50, 'x'); // То же самое

При принятии решения об использовании функций размещения стоит иметь в виду еще пару вопросов. Первый из них связан с управлением ресурсами. Предположим, что у вас есть контейнер с объектами >std::shared_ptr

>std::list> ptrs;

и вы хотите добавить >std::shared_ptr, который должен быть освобожден с помощью пользовательского удалителя (см. раздел 4.2). В разделе 4.4 поясняется, что по возможности вы должны использовать для создания >std::shared_ptr функцию >std::make_shared, но в нем же указано, что существуют ситуации, когда это невозможно. Одна из таких ситуаций — когда вы хотите указать пользовательский удалитель. В этом случае для получения обычного указателя, которым будет управлять интеллектуальный указатель >std::shared_ptr, вы должны непосредственно использовать оператор >new.

Если пользовательский удалитель представляет собой функцию

>void killWidget(Widget* pWidget);

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

>ptrs.push_back(std::shared_ptr(new Widget, killWidget));

Он может также принять и такой вид, означающий то же самое:

>ptrs.push_back({ new Widget, killWidget });

В любом случае перед вызовом >push_back будет создан временный объект >std::shared_ptr. Параметром >push_back является ссылка на >std::shared_ptr, так что должен быть объект, на который ссылается этот параметр.

Функция >emplace_back должна избегать создания временного объекта >std::shared_ptr, но в этом случае ценность временного объекта гораздо выше его стоимости. Рассмотрим следующую потенциальную последовательность событий.

1. В любом из приведенных выше вызовов конструируется временный объект >std::shared_ptr, хранящий простой указатель, являющийся результатом операции “>new Widget”. Назовем этот объект >temp.

2. Функция >push_back получает >temp по ссылке. В процессе выделения памяти для узла списка, который должен содержать копию >temp, генерируется исключение нехватки памяти.

3. При выходе исключения за пределы >push_back объект >temp уничтожается. Поскольку это единственный интеллектуальный указатель >std::shared_ptr, указывающий на управляемый им объект >Widget, он автоматически удаляет последний, в данном случае с помощью вызова >killWidget.

Несмотря на происшедшую генерацию исключения нет никаких утечек: >Widget, созданный с помощью “>new Widget” в вызове >push_back, освобождается деструктором объекта >std::shared_ptr, который был создан для управления им (объектом >temp). Все отлично.

Рассмотрим теперь, что произойдет при вызове >emplace_back вместо >push_back:

>ptrs.emplace_back(new Widget, killWidget);

1. Обычный указатель, являющийся результатом выполнения “>new Widget”, передается с помощью прямой передачи в точку внутри >emplace_back, где выделяется память для узла списка. При попытке выделения памяти генерируется исключение нехватки памяти.

2. При выходе исключения за пределы >emplace_back обычный указатель, который был единственным средством доступа к >Widget в динамической памяти, оказывается потерянным. Происходит утечка >Widget (и всех ресурсов, которыми владеет этот объект).

В этом сценарии все совсем не отлично, и класс >std::shared_ptr в этом не повинен. Та же самая проблема возникнет при использовании >std::unique_ptr с пользовательским удалителем. По существу, эффективность классов управления ресурсами, таких как >std::shared_ptr и >std::unique_ptr, основана на немедленной передаче ресурсов (таких, как обычные указатели, возвращаемые оператором >new) конструкторам управляющих ресурсами объектов. Тот факт, что функции наподобие >std::make_shared и >std::make_unique автоматизируют этот процесс, является одной из причин, по которым эти функции так важны.

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


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