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

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

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

Откровенно говоря, вы в любом случае не должны передавать выражения наподобие “>new Widget” в функции >emplace_back и >push_back, как и в большинство любых других функций, поскольку, как поясняется в разделе 4.4, это ведет к возможным проблемам с безопасностью исключений, одну из которых мы только что рассмотрели. Для предотвращения неприятностей требуется, чтобы получение указателя от “>new Widget” и превращение его в управляющий ресурсом объект выполнялось в одной инструкции, а уже затем этот объект передавался как rvalue функции, которой вы хотели изначально передавать “>new Widget”. (Более детально этот подход рассматривается в разделе 4.4.) Таким образом, код, использующий >push_back, должен быть записан скорее как

>std::shared_ptr spw(new Widget,  // Создание Widget и

>                            killWidget); // передача его spw

>ptrs.push_back(std::move(spw));          // Добавление spw

>                                         // как rvalue

Версия с использованием >emplace_back аналогична:

>std::shared_ptr spw(new Widget, killWidget);

>ptrs.emplace_back(std::move(spw));

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

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

>std::vector regexes;

Отвлекшись на ссору ваших коллег о том, как часто следует проверять свой Facebook, вы случайно написали следующий, казалось бы, бессмысленный код:

>regexes.emplace_back(nullptr);

Вы не заметили ошибку при вводе, компилятор скомпилировал его без замечаний, так что вам пришлось потратить немало времени на отладку. В какой-то момент вы обнаружили, что вставляете в контейнер регулярных выражений нулевой указатель. Но как это возможно? Указатели не являются регулярными выражениями, и если вы попытаетесь написать что-то вроде

>std::regex r = nullptr;// Ошибка! Не компилируется!

компилятор отвергнет такой код. Интересно, что будет отвергнут и вызов >push_back вместо >emplace_back:

>regexes.push_back(nullptr); // Ошибка! Не компилируется!

Такое любопытное поведение поясняется тем, что объекты >std::regex могут быть построены из символьных строк. Это делает корректным такой полезный код, как

>std::regex upperCaseWord("[A-Z]+");

Создание >std::regex из символьной строки может иметь достаточно высокую стоимость, так что для минимизации вероятности непреднамеренных расходов конструктор >std::regex, принимающий указатель >const char*, объявлен как >explicit. Именно поэтому не компилируются следующие строки:

>std::regex r = nullptr;     // Ошибка! Не компилируется!

>regexes.push_back(nullptr); // Ошибка! Не компилируется!

В обоих случаях требуется неявное преобразование указателя в >std::regex, а объявление конструктора как >explicit его предотвращает.

Однако в вызове >emplace_back мы передаем не объект >std::regex, а аргументы конструктора объекта >std::regex. Это не рассматривается как запрос неявного преобразования. Компилятор трактует этот код так, как если бы вы написали

>std::regex r(nullptr); // Компилируется

Если лаконичный комментарий “Компилируется” кажется вам лишенным энтузиазма, то это хорошо, потому что, несмотря на компилируемость, данный код имеет неопределенное поведение. Конструктор >std::regex, принимающий указатель >const char*, требует, чтобы этот указатель был ненулевым, а nullptr подчеркнуто нарушает данное требование. Если вы напишете и скомпилируете такой код, лучшее, на что вы можете надеяться, — аварийное завершение программы во время выполнения. Если вы не такой счастливчик, то вам предстоит получить немалый опыт работы с отладчиком.

На минутку оставляя без внимания >push_back и >emplace_back, обратим внимание на то, что очень похожие синтаксисы инициализации дают совершенно разные результаты:

>std::regex r1 = nullptr; // Ошибка! Не компилируется

>std::regex r2


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