С. Мейерс - Эффективный и современный C++ (1114942), страница 30
Текст из файла (страница 30)
. . Ts>std: : unique_ytr<Investment,decltype (clelinvmt) >1 1 Исправленный:1 1 возвращаемый типmakelnvestment (Ts&& . . . params )std : : unique_ptr<Investment ,decltype (dellnvmt ) > / / Возвращаемый// указательp l nv ( nullpt r, del l nvmt ) ;i f ( /* Должен быть создан объект Stock * / )p lnv .
reset ( new Stock ( st d : : forward<Ts> ( params ) . . . ) );else i f ( /* Должен быть создан объект Bond * / )1Из этого правила есть несколько исключений. Большинство из них обусловпено аварийным завершс·нием программы. Если исключение выходит за пределы основной фу11ю1ии потока (например, ma i nв случае начального потока программы) или если нарушена спецификация noexcept (см.
ра.1дс11 3.8),локальные объекты могут не быть уничтожены; они, определенно, нс уничтожаются, когда вызывается функция std: : abort или функция выхода (т.е. s t d : : _E x i t , s td : : exit или std : : quick_e x i t ) .1 28Глава 4. Интеллектуальные указателиplnv . reset ( new Bond ( st d : : forward<Ts> (params ) . . . ) ) ;else i f ( / * Должен быть создан объект RealEstate * / )plnv . reset (new RealEstate ( s td : : forward<Ts > (params ) .
. . ) ) ;return plnv;Сейчас я поясню вам, как это работает, но сначала рассмотрим, как все это выглядит с точки зрения вызывающей функции. Предположим, что вы сохраняете результатвызова ma keinvestment в переменной, объявленной как auto, и тем самым остаетесьв блаженном неведении о том, что используемый вами ресурс требует специальной обработки в процессе удаления. Вы просто купаетесь в этом блаженстве, поскольку использование s t d : : unique_pt r означает, что вам не надо рассматривать самостоятельновопросы освобождения ресурса, тем более не требуется беспокоиться о том, чтобы этоуничтожение выполнялось в точности один раз на каждом пути в программе.
Обо всемэтом std : : un i que_pt r заботится автоматически. С точки зрения клиента интерфейсmake investment - просто конфетка.Реализация очень красивая, если вы понимаете следующее.•del lnvmt представляет собой пользовательский удалитель для объекта, возвращаемого функцией make investment. Все функции пользовательских удалителей принимают обычный указатель на удаляемый объект и затем выполняют все необходимые действия по его удалению. В нашем случае действие заключается в вызовеmakeLogEnt ry и последующем применении delete.
Применение лямбда-выражения для создания del l nvmt удобно, но, как вы вскоре увидите, оно также гораздоэффективнее написания обычной функции.•Когда используется пользовательский удалитель, его тип должен быть указанв качестве второго аргумента типа s t d : : un i que_p t r. В нашем случае это типde l i nvmt , и именно поэтому возвращаемым типом ma ke l nvestment являетсяstd : : unique_ptr<Investment , decltype ( de l lnvmt ) >. (О том, что такое decltype,рассказывается в разделе 1 .3.)•Основная стратегия make l nvestment состоит в создании нулевого указателяstd : : unique_pt r, после чего он делается указывающим на объект соответствующего типа и возвращается из функции. Для связи пользовательского удалителяdel l nvmt с pinv мы передаем его в качестве второго аргумента конструктора.•Попытка присвоить обычный указатель (например, возвращенный оператором new)указателю s t d : : uni que_pt r компилироваться не будет, поскольку она будет содержать неявное преобразование обычного указателя в интеллектуальный.
Такие неявные преобразования могут быть проблематичными, так что интеллектуальные указатели С++ 1 1 их запрещают. Вот почему для того, чтобы pinv взял на себя владениеобъектом, созданным с помощью оператора new, применяется вызов reset.4. 1 .Используйте std::uпique_ptr дл я управления ресурсами путем исключительного впадения1 29•С каждым использованием new мы применяем std : : forward для прямой передачиаргументов, переданных в make i nvestment (см.
раздел 5.3). Это делает всю информацию, предоставляемую вызывающей функцией, доступной конструкторам создаваемых объектов.•Пользовательский удалитель получает параметр типа I nv e s t m e n t * . Независимо от фактического типа объекта, создаваемого в функции ma ke i nv e stment (т.е.St ock, Bond или RealEstate ) , он в конечном итоге будет удален с помощью оператора de l e t e в лямбда-выражении как объект I nve s tment * . Это означает, что мыудаляем производный класс через указатель на базовый класс.
Чтобы это сработало, базовый класс I nvestment должен иметь виртуальный деструктор:class InvestmentpuЫ i c :virtual �Investment ( ) ; / / Важная часть дизайна};В С++ 14 существование вывода возвращаемого типа функции (раздел 1 .3) означает,что ma ke i nvestme n t можно реализовать проще и несколько более инкапсулированно:template<typename . . . Ts>auto makeinvestment (Ts&& . .
. params ) / / С++ 1 4auto del i nvmt = [ ] ( Inves tment * pinvestmen t )/ / Теперь размещается в{makeLogEntry (pinvestment ) ; / / пределах ma keinvestmentdelete pinve stment;};std: : unique__ptr< I nves tment , decltype (de l i nvmt ) >/ / Как и ранееpinv ( nullptr, del invmt ) ;/ / Как и ранееif ( ". )pinv . reset ( new Stock ( std: : forward<Ts> (params ) . . . ) ) ;else i f ( ". )11 Как и ранее{pinv . reset ( new Bond ( std: : forward<Ts> (params ) . . . ) ) ;else i f ( ." )/ / Как и ранее{p inv .
reset ( new RealEstate ( std : : forward<Ts> (params ) . . . ) ) ;return pinv;1 30/ / Как и ранееГn ава 4. Интеnnектуаnьные указатеnиРанее я отмечал, что при использовании удалителя по умолчанию (т.е. delete) можноразумно предположить, что объекты std : : unique_ptr имеют тот же размер, что и обычные указатели. Когда в игру вступают пользовательские указатели, это предположениеперестает быть верным. Удалители являются указателями на функции, которые в общемслучае приводят к увеличению размера s t d : : un i que _pt r на слово или два. Для удалителей, являющихся функциональными объектами, изменение размера зависит от того,какое состояние хранится в функциональном объекте.
Функциональные объекты безсостояний (например, получающиеся из лямбда-выражений без захватов) не приводятк увеличению размеров, а это означает что когда пользовательский удалитель можетбыть реализован как функция или как лямбда-выражение, то реализация в виде лямбдавыражения предпочтительнее:auto del l nvrnt l[]( Investment * p l nvestmen t )1 1 Пользовательский удалительmakeLogEntry ( plnvestmen t ) ; / / как лямбда - выражение/ / без состоянияdelete plnvestment ;=f;template<typename .
. . Ts>std: : Шlique__ptr<Investment ,decltype (delinvmtl) >1 1 Возвращаемый тип имеет размер1 1 Investment*makelnvestment ( Ts & & . . . args ) ;void del l nvrnt2 ( Investment* plnvestmen t ) / / Пользовательский// удалитель как/ / функцияmakeLogEntry (plnvestment ) ;delete plnvestment ;1 1 Возвращаемый тип имеет размер// Investment* плюс как минимумvoid (*) ( Investment* ) > 1 1 размер указателя на функцию !makelnvestment (Ts&& . .
. params ) ;template<typename . . . Ts>std: : Шlique__p tr<Investment ,Удалители в виде функциональных объектов с большим размером состояния могутпривести к значительным размерам объектов std: : unique_ptr. Если вы обнаружите, чтопользовательский удалитель делает ваш интеллектуальный указатель std : : un i qu e _pt rнеприемлемо большим, вам, вероятно, стоит изменить свой дизайн.Фабричные функции - не единственный распространенный способ использованияs t d : : unique_ptr. Эти интеллектуальные указатели еще более популярны в качестве механизма реализации идиомы Pimpl (указателя на реализацию).
Соответствующий код несложен, но его рассмотрение отложено до раздела 4.5, посвященного данной теме.4. 1 .Используйте std::uпique_ptr дл я управления ресурсами путем исключительного владения131И нтеллектуальный указатель s td : : u n i que _pt r имеет две разновидности: одну для индивидуальных объектов ( s t d : : un i que_pt r < T > }, а другую - для массивов(std : : unique _pt r<T [ ] >). В результате никогда не возникает неясность в том, на какую сущность указывает s t d : : un i que_p t r.
API s t d : : un i que_p t r разработан так,чтобы соответствовать используемой разновидности. Например, в случае указателядля одного объекта отсутствует оператор индексирования (operator [ ] ), в то времякак в случае указателя для массива отсутствуют операторы разыменования (ope rator*и operator- >).Существование std : : unique_pt r для массивов должно представлять только интеллектуальный интерес, поскольку std : : array, std : : vector и std : : s tring почти всегдаоказываются лучшим выбором, чем встроенные массивы. Я могу привести только однуситуацию, когда s t d : : unique_pt r<T [ ] > имеет смысл - при использовании С-образногоAPI, который возвращает встроенный указатель на массив в динамической памяти, которым вы будете владеть.Интеллектуальный указатель std : : unique _ptr представляет собой способ выраженияисключительного владения на С++ 1 1 , но одной из наиболее привлекательных возможностей является та, что его можно легко и эффективно преобразовать в std : : shared_ptr:11 Конвертация std : : unique_pt rmakeinvestment ( arguments ) ; 11 в std : : sha red_pt rstd: : shared_ytr<Investment> sp=Это ключевой момент, благодаря которому std : : unique_pt r настолько хорошо подходит для возвращаемого типа фабричных функций.
Фабричные функции не могут знать,будет ли вызывающая функция использовать семантику исключительного владения возвращенным объектом или он будет использоваться совместно (т.е. std : : shared_pt r).Возвращая s t d: : un i que_pt r, фабрики предоставляют вызывающим функциям наиболееэффективный интеллектуальный указатель, но не мешают им заменить этот указательболее гибким. (Информация об интеллектуальном указателе s t d : : shared_ptr приведенав разделе 4.2.)Сл едует запомнить•s t d : : u n i que_pt r представляет собой маленький, быстрый, предназначенныйтолько для перемещения интеллектуальный указатель для управления ресурсамис семантикой исключительного владения.•По умолчанию освобождение ресурсов выполняется с помощью оператора delete,но могут применяться и пользовательские удалители. Удалители без состоянийи указатели на функции в качестве удалителей увеличивают размеры объектовstd : : unique_ptr.•Интеллектуальные указатели std : : unique_pt r легко преобразуются в интеллектуальные указатели std : : shared_pt r.1 32Глава 4.