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

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

> // возвращаемый тип

>                decltype(delInvmt)>

>makeInvestment(Ts&&... params) {

> std::unique_ptr

>                 decltype(delInvmt)> // Возвращаемый

> pInv(nullptr, delInvmt);            // указатель

> if ( /* Должен быть создан объект Stock */ ) {

>  pInv.reset(new Stock(std::forward(params)...));

> }

> else if ( /* Должен быть создан объект Bond */ ) {

>  pInv.reset(new Bond(std::forward(params)...));

> }

> else if ( /* Должен быть создан объект RealEstate */ ) {

>  pInv.reset(new RealEstate(std::forward(params)...));

> }

> return pInv;

>}

Сейчас я поясню вам, как это работает, но сначала рассмотрим, как все это выглядит с точки зрения вызывающей функции. Предположим, что вы сохраняете результат вызова >makeInvestment в переменной, объявленной как >auto, и тем самым остаетесь в блаженном неведении о том, что используемый вами ресурс требует специальной обработки в процессе удаления. Вы просто купаетесь в этом блаженстве, поскольку использование >std::unique_ptr означает, что вам не надо рассматривать самостоятельно вопросы освобождения ресурса, тем более не требуется беспокоиться о том, чтобы это уничтожение выполнялось в точности один раз на каждом пути в программе. Обо всем этом >std::unique_ptr заботится автоматически. С точки зрения клиента интерфейс >makeInvestment — просто конфетка.

Реализация очень красивая, если вы понимаете следующее.

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

• Когда используется пользовательский удалитель, его тип должен быть указан в качестве второго аргумента типа >std::unique_ptr. В нашем случае это тип >delInvmt, и именно поэтому возвращаемым типом >makeInvestment является >std::unique_ptr. (О том, что такое >decltype, рассказывается в разделе 1.3.)

• Основная стратегия >makeInvestment состоит в создании нулевого указателя >std::unique_ptr, после чего он делается указывающим на объект соответствующего типа и возвращается из функции. Для связи пользовательского удалителя >delInvmt с >pInv мы передаем его в качестве второго аргумента конструктора.

• Попытка присвоить обычный указатель (например, возвращенный оператором >new) указателю >std::unique_ptr компилироваться не будет, поскольку она будет содержать неявное преобразование обычного указателя в интеллектуальный. Такие неявные преобразования могут быть проблематичными, так что интеллектуальные указатели С++11 их запрещают. Вот почему для того, чтобы >pInv взял на себя владение объектом, созданным с помощью оператора >new, применяется вызов >reset.

• С каждым использованием >new мы применяем >std::forward для прямой передачи аргументов, переданных в >makeInvestment (см. раздел 5.3). Это делает всю информацию, предоставляемую вызывающей функцией, доступной конструкторам создаваемых объектов.

• Пользовательский удалитель получает параметр типа >Investment*. Независимо от фактического типа объекта, создаваемого в функции >makeInvestment (т.e. >Stock, >Bond или >RealEstate), он в конечном итоге будет удален с помощью оператора >delete в лямбда-выражении как объект >Investment*. Это означает, что мы удаляем производный класс через указатель на базовый класс. Чтобы это сработало, базовый класс >Investment должен иметь виртуальный деструктор:

>class Investment {

>public:

> …

> virtual ~Investment(); // Важная часть дизайна

>};

В С++14 существование вывода возвращаемого типа функции (раздел 1.3) означает, что >makeInvestment можно реализовать проще и несколько более инкапсулированно:

>template

>auto makeInvestment(Ts&&... params) // С++14

>{

> auto delInvmt = [](Investment* pInvestment)

> {                           // Теперь размещается в

>  makeLogEntry(pInvestment); // пределах makeInvestment

>  delete pInvestment;

> };


> std::unique_ptr

>  pInv(nullptr, delInvmt);  // Как и ранее

> if ( … )                   // Как и ранее

>  pInv.reset(new Stock(std::forward(params)...));

> else if ( … )              // Как и ранее

>  pInv.reset(new Bond(std::forward(params)...));

> else if ( … )              // Как и ранее

>  pInv.reset(new RealEstate(std::forward(params)...));

> return pInv;               // Как и ранее

>}

Ранее я отмечал, что при использовании удалителя по умолчанию (т.e. >delete) можно разумно предположить, что объекты >std::unique_ptr имеют тот же размер, что и обычные указатели. Когда в игру вступают пользовательские указатели, это предположение перестает быть верным. Удалители являются указателями на функции, которые в общем случае приводят к увеличению размера >std::unique_ptr на слово или два. Для удалителей, являющихся функциональными объектами, изменение размера зависит от того, какое состояние хранится в функциональном объекте. Функциональные объекты без состояний (например, получающиеся из лямбда-выражений без захватов) не приводят к увеличению размеров, а это означает что когда пользовательский удалитель может быть реализован как функция или как лямбда-выражение, то реализация в виде лямбда- выражения предпочтительнее:


Еще от автора Скотт Мейерс
Эффективное использование STL

В этой книге известный автор Скотт Мейерс раскрывает секреты настоящих мастеров, позволяющие добиться максимальной эффективности при работе с библиотекой STL.Во многих книгах описываются возможности STL, но только в этой рассказано о том, как работать с этой библиотекой. Каждый из 50 советов книги подкреплен анализом и убедительными примерами, поэтому читатель не только узнает, как решать ту или иную задачу, но и когда следует выбирать то или иное решение — и почему именно такое.


Как функции, не являющиеся методами, улучшают инкапсуляцию

Когда приходится инкапсулировать, то иногда лучше меньше, чем большеЯ начну со следующего утверждения: Если вы пишете функцию, которая может быть выполнена или как метод класса, или быть внешней по отношению к классу, Вы должны предпочесть ее реализацию без использования метода. Такое решение увеличивает инкапсуляцию класса. Когда Вы думаете об использовании инкапсуляции, Вы должны думать том, чтобы не использовать методы.Удивлены? Читайте дальше.


Рекомендуем почитать
Изучаем Java EE 7

Java Enterprise Edition (Java EE) остается одной из ведущих технологий и платформ на основе Java. Данная книга представляет собой логичное пошаговое руководство, в котором подробно описаны многие спецификации и эталонные реализации Java EE 7. Работа с ними продемонстрирована на практических примерах. В этом фундаментальном издании также используется новейшая версия инструмента GlassFish, предназначенного для развертывания и администрирования примеров кода. Книга написана ведущим специалистом по обработке запросов на спецификацию Java EE, членом наблюдательного совета организации Java Community Process (JCP)


Платформа J2Me

Эта книга научит вас, как разрабатывать программное обеспечение для платформы J2ME компании «Sun Microsystems». Эта книга придерживается стиля учебного пособия, это не справочное руководство.Цель — дать вам твердую основу в понятиях и техниках, которая даст вам возможность решиться на самостоятельную разработку качественных приложений.


Виртуальная библиотека Delphi

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


Обработка баз данных на Visual Basic.NET

Это практическое руководство разработчика программного обеспечения на Visual Basic .NET и ADO.NET, предназначенное для создания приложений баз данных на основе WinForms, Web-форм и Web-служб. В книге описываются практические способы решения задач доступа к данным, с которыми сталкиваются разработчики на Visual Basic .NET в своей повседневной деятельности. Книга начинается с основных сведений о создании баз данных, использовании языка структурированных запросов SQL и системы управления базами данных Microsoft SQL Server 2000.


Исчерпывающее руководство по написанию всплывающих подсказок

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


Программное обеспечение встроенных систем. Общие требования к разработке и документированию

Embedded system software. General requirements for development and documentationСтандарт подготовлен в развитие ГОСТ Р ИСО/МЭК 12207-99 «Информационная технология. Процессы жизненного цикла программных средств» с целью учета специфики разработки и документирования программного обеспечения встроенных систем реального времени.