С. Мейерс - Эффективный и современный C++ (1114942), страница 34
Текст из файла (страница 34)
Одной разновидностью являетсяs t d : : weak_pt r : : l oc k, которая возвращает s t d : : shared_pt r. Этот указатель нулевой,если std: : weak_pt r просрочен:std : : shared_ptr<Widget> spwl= wpw . lock ( ) ;auto spw2 = wpw . lock ( ) ;/ / Если wpw просрочен,11 spwlнулевой11 То же самое, но с auto-Второй разновидностью является конструктор s t d : : shared p t r, принимающийstd : : weak_pt r в качестве аргумента. В этом случае, если s t d : : weak_pt r просрочен, генерируется исключение:std: : shared_J>tr<Widget> spwЗ ( wpw) ; / / Если wpw просрочен, гене// рируется std : : bad_weak_ptrНо вас, вероятно, интересует, зачем вообще нужен s t d : : weak_ptr.
Рассмотрим фабричную функцию, которая производит интеллектуальные указатели на объекты толькодля чтения на основе уникальных значений идентификаторов. В соответствии с советомиз раздела 4. 1 , касающегося возвращаемых типов фабричных функций, она возвращаетstd : : unique_ptr:std : : unique_ptr<const Widget> loadWidget (WidgetID id) ;Если loadWidget является дорогостоящим вызовом (например, из-за файловых операций ввода-вывода или обращения к базе данных), а идентификаторы часто используются4.3. Испопьзуйте std::weak_ptr для std::shared_ptr-noдoбныx указателей, которые моrут быть висячими1 43повторно, разумной оптимизацией будет написание функции, которая делает то же, чтои l oadW idget, но при этом кеширует результаты.
Засорение кеша всеми затребованнымиWidget может само по себе привести к проблемам производительности, так что другойразумной оптимизацией является удаление кешированных Widget, когда они больше неиспользуются.Для такой кеширующей фабричной функции возвращаемый тип s t d : : unique_ptrне является удачным выбором. Вызывающий код, определенно, получает интеллектуальные указатели на кешированные объекты, и время жизни полученных объектовтакже определяется вызывающим кодом.
Однако кеш также должен содержать указатели на эти же объекты. Указатели кеша должны иметь возможность обнаруживать своевисячее состояние, поскольку когда клиенты фабрики заканчивают работу с объектом,возвращенным ею, этот объект уничтожается, и соответствующая запись кеша становится висячей. Следовательно, кешированные указатели должны представлять собойуказатели s t d : : weak_pt r, которые могут обнаруживать, что стали висячими.
Это означает, что возвращаемым типом фабрики должен быть s t d : : shared_pt r, так как указатели std: : weak_pt r могут обнаруживать, что стали висячими, только когда время жизниобъектов управляется указателями std : : shared_ptr.Вот как выглядит быстрая и неаккуратная реализация кеширующей версииloadWidget:std: : shared_ptr<const Widget> fastLoadWidget (Widget ID id)stati c std : : unordered_map<WidgetID,std: : weakytr<const Widget>> cache ;auto obj Ptrcache [ id] . lock ( ) ;=111111111111obj Ptr является std: : shared_ptrдля кешированного объекта инулевым указателем для объекта ,отсутствующего в кешеПри отсутствии в кешеобъект загружаетсяi f ( ! obj Ptr) {obj PtrloadWidget ( id) ;1 1 и кешируетсяcache [ id] = obj Ptr;=return obj Ptr;Эта реализация использует один из контейнеров С++ 1 1, представляющий собой хештаблицу ( std : : unordered_map}, хотя здесь и не показаны хеширование Widge t I D и функции сравнения, которые также должны присутствовать в коде.Реализация fastLoadW i dget игнорирует тот факт, что кеш может накапливать просроченные указатели s t d : : wea k_ptr, соответствующие объектам Widget, которые больше не используются (а значит, были уничтожены).
Реализация может быть улучшена,но вместо того чтобы тратить время на вопрос, который не привнесет ничего новогов понимание интеллектуальных указателей std : : weak_pt r, давайте рассмотрим второе1 44Глава 4. Интеллектуальные указателиприменение этих указателей: шаблон проектирования Observer (Наблюдатель). Основными компонентами этого шаблона являются субъекты (объекты, которые могут изменяться) и наблюдатели (объекты, уведомляемые при изменении состояний). В большинстве реализаций каждый субъект содержит член-данные, хранящие указатели на егонаблюдателей.
Это упрощает для субъектов проблемы уведомления об изменении состояний. Субъекты не заинтересованы в управлении временем жизни своих наблюдателей(т.е. тем, когда они должны быть уничтожены), но они очень заинтересованы в том, чтобы, если наблюдатель был уничтожен, субъекты не пытались к нему обратиться. Разумным проектом может быть следующий - каждый субъект хранит контейнер указателейstd : : weak_pt r на своих наблюдателей, тем самым позволяя субъекту определять, не является ли указатель висящим, перед тем как его использовать.В качестве последнего примера применения s td : : wea k_pt r рассмотрим структуруданных с объектами А, В и С в ней, где А и С совместно владеют В, а следовательно, хранятуказатели std : : shared_ptr на нее:Г7l std : : shared ptrstd : : shared ptrГг11r;1i2.Ji--------�L!.J�---------�,-Предположим, что было бы также полезно иметь указатель из в на А.
Какую разновидность интеллектуального указателя следует использовать в этом случае?Есть три варианта.•Обычный указатель. При таком подходе, если уничтожается А, а с продолжаетуказывать на В, В будет содержать указатель на А, который становится висящим. Вне в состоянии этого определить, а потому В может непреднамеренно этот указатель разыменовать. В результате получается неопределенное поведение.•Указатель std: : shared_ytr.•Это позволяет избежать обеих описанных выше проблем. Если уничтожается А, указатель в в становится висящим, но В в состоянииэто обнаружить.
Кроме того, хотя А и В указывают друг на друга, указатель в В невлияет на счетчик ссылок А, а следовательно, не может предотвратить удаление А,когда на него больше не указывает ни один std : : shared_pt r.В этом случае А и В содержат указатели std: : shared_pt rодин на другой. Получающийся цикл std: : shared_ptr (А указывает на В, а В указывает на А) предохраняет и А, и В от уничтожения. Даже если А и В недостижимы из других структур данных программы (например, поскольку С больше не указывает на В),счетчик ссылок каждого из них равен единице. Если такое происходит, А и В оказываются потерянными для всех практических применений: программа не в состояниик ним обратиться, а их ресурсы не моrут быть освобождены.Указатель std : : weak_ytr.4.3. Испоnьэуйте std::weak_ptr дnя std::shared_ptr-noдoбныx указателей, которые могут быть висячими1 45Очевидно, что наилучшим выбором является std : : weak_pt r.
Однако стоит отметить,что необходимость применения указателей std : : weak_ptr для предотвращения потенциальных циклов из указателей s t d : : shared_pt r не является очень распространеннымявлением. В строго иерархических структурах данных, таких как деревья, дочернимиузлами обычно владеют их родительские узлы. При уничтожении родительского узладолжны уничтожаться и его дочерние узлы.
В общем случае связи от родительских к дочерним узлам лучше представлять указателями s t d : : unique _pt r. Обратные связи от дочерних узлов к родительским можно безопасно реализовывать, как обычные указатели,поскольку дочерний узел никогда не должен иметь время жизни, большее, чем времяжизни его родительского узла. Таким образом, отсутствует риск того, что дочерний узелразыменует висячий родительский указатель.Конечно, не все структуры данных на основе указателей строго иерархичны, и когда приходится сталкиваться с такими неиерархичными ситуациями, как и с ситуацияминаподобие кеширования или реализации списков наблюдателей, знайте, что у вас естьтакой инструмент, как std : : weak_pt r.С точки зрения эффективности std : : wea k_ptr, по сути, такой же, как и std : : shared_ptr.
Объекты s t d : : weak_pt r имеют тот же размер, •по и объекты std: : shared_ptr, онииспользуют те же управляющие блоки, что и указатели s t d : : shared_pt r (см. раздел 4.2),а операции, такие как создание, уничтожение и присваивание, включают атомарную работу со счетчиком ссылок. Вероятно, это вас удивит, поскольку в начале этого разделая писал, что указатели s t d : : weak_ptr не участвуют в подсчете ссылок. Но это не совсемто, что я написал. Я написал, что указатели s t d : : weak_pt r не участвуют в совместномвла д ении объектами, а следовательно, не влияют на счетчик ссылок указываемого о бъекта. На самом деле в управляющем блоке имеется второй счетчик ссылок, и именнос ним и работают указатели s t d : : weak_ptr.
Более подробно этот вопрос рассматривается в разделе 4.4._Следует запомнить•Используйте std : : weak_pt r как std : : shared_pt r-oбpaзныe указатели, которыемогут быть висячими.•Потенциальные применения std : : wea k_pt r включают хеширование, списки наблюдателей и предупреждение циклов указателей s t d : : shared_pt r.4.4.
П редпочитайте испоnьзование s td : : make_uniqueи s td : : make_shared непосредственномуиспоnьзованию оператора newНачнем с выравнивания игровой площадки для игры s t d : : ma ke_un i que и s td : :make_shared против обычных указателей. Функция std : : ma ke_shared является частьюC++ l l , но, увы, s t d : : ma ke_unique таковой не является. Она вошла в стандарт только1 46Глава 4. И нтеллектуальные указателиначиная с С++ 14. Если вы используете С++ 1 1 , не переживайте, потому что базовую версию s t d : : ma ke _unique легко написать самостоятельно. Смотрите сами:template<typename Т, typename .