С. Мейерс - Эффективный и современный C++ (1114942), страница 33
Текст из файла (страница 33)
Онаприменяет наследование, и при этом даже имеются виртуальные функции. (Все это требуется для того, чтобы обеспечить корректное уничтожение указываемого объекта.) Этоозначает, что применение указателей std : : shared_pt r берет на себя также стоимостьмеханизма виртуальной функции, используемой управляющим блоком.Возможно, после того как вы прочли о динамически выделяемых управляющих блоках, удалителях и распределителях неограниченного размера, механизме виртуальныхфункций и атомарности работы со счетчиками ссылок, ваш энтузиазм относительноs t d : : shared_ptr несколько угас.
Это нормально.Они не являются наилучшим решением для любоf! задачи управления ресурсами. Нодля предоставляемой ими функциональности цена std : : shared_pt r весьма разумна.В типичных условиях, когда использованы удалитель и распределитель памяти по умолчанию, а s t d : : shared_pt r создается с помощью s t d : : ma ke_shared, размер управляющего блока составляет около трех слов, и его выделение, по сути, ничего не стоит.
(Оно1 40Глава 4. Интеллектуальные укаэателивстроено в выделение памяти для указываемого им объекта. Дополнительная информация об этом приведена в разделе 4.4.) Разыменование std : : shared_ptr не более дорогостояще, чем разыменование обычного указателя. Выполнение операций, требующих работы со счетчиком ссылок (например, копирующий конструктор или копирующее присваивание, удаление) влечет за собой одну или две атомарные операции, но эти операцииобычно отображаются на отдельные машинные команды, так что, хотя они могут бытьдороже неатомарных команд, они все равно остаются отдельными машинными командами.
Механизм виртуальных функций в управляющем блоке обычно используется толькооднажды для каждого объекта, управляемого указателями s t d : : s hared_ptr: когда происходит уничтожение объекта.В обмен на эти весьма скромные расходы вы получаете автоматическое управлениевременем жизни динамически выделяемых ресурсов. В большинстве случаев применениеstd : : shared_ptr значительно предпочтительнее, чем ручное управление временем жизни объекта с совместным владением.
Если вы сомневаетесь, можете ли вы позволить себеиспользовать s t d : : shared_ptr, подумайте, точно ли вам нужно обеспечить совместноевладение. Если вам достаточно или даже может бь1ть достаточно исключительноговладения, лучшим выбором является std : : unique ptr. Его профиль производительности близок к таковому для обычных указателей, а "обновление" std : : un i que_pt rдо std : : shared_ptr выполняется очень легко, так как указатель std : : shared_ptr можетбыть создан из указателя std : : unique_ptr.Обратное неверно. После того как вы включили управление временем жизни ресурса с помощью std : : shared_pt r, обратной дороги нет. Даже если счетчик ссылок равен единице, нельзя вернуть владение ресурсом для того, чтобы, скажем, им управлялstd : : unique_ptr.
Контракт владения между ресурсом и указателями s t d : : sha red_ptr,которые указывают на ресурс, написан однозначно - "пока смерть не разлучит нас': Никаких разводов и раздела имущества не предусмотрено.Есть еще кое-что, с чем не могут справиться указатели std : : shared_pt r, - массивы.Это еще одно их отличие от указателей std : : unique_ptr. Класс s t d : : shared_ptr имеетAPI, предназначенное только для работы с указателями на единственные объекты. Не существует s t d : : shared_ptr<T [ ] >. Время от времени "крутые" программисты натыкаютсяна мысль использовать std : : shared_ptr<T> для указания на массив, определяя пользовательский удалитель для выполнения освобождения массива (т.е. delete [ ] ).
Это можносделать и скомпилировать, но это ужасная идея. С одной стороны, класс std: : shared_pt rне предоставляет оператор operator [ ] , так что индексирование указываемого массиватребует неудобных выражений с применением арифметики указателей. С другой стороны, std : : shared_ptr поддерживает преобразование указателей на производные классыв указатели на базовые классы, которое имеет смысл только для одиночных объектов,но при применении к массивам оказывается открытой дырой в системе типов. (По этойпричине API std : : unique_ptr<T [ ] > запрещает такие преобразования.) Что еще болееважно, учитывая разнообразие альтернатив встроенным массивам в С++ 1 1 (например,std : : array, std : : vector, std : : string), объявление интеллектуального указателя на тупой массив всегда является признаком плохого проектирования.4.2.Используйте std::shared_ptr для управления ресурсами путем совместноrо владения1 41Следует запомн ить•s t d : : sha red_p t r предоставляет удобный подход к управлению временем жизнипроизвольных ресурсов, аналогичный сборке мусора.•По сравнению с s t d : : un i que_p t r объекты st d : : shared_p t r обычно в два разабольше, привносят накладные расходы на работу с управляющими блоками и требуют атомарной работы со счетчиками ссылок.•Освобождение ресурсов по умолчанию выполняется с помощью оператора delete,однако поддерживаются и пользовательские удалители.
Тип удалителя не влияетна тип указателя s t d : : shared_pt r.•Избегайте создания указателей s t d : : sha red_p t r из переменных, тип которых обычный встроенный указатель.4.3 . Испопьзуйте s td : : weak_ptrдпя s td : : shared_рtr- подобных указа тепей,которые моrут бы ть висячимиПарадоксально, но может быть удобно иметь интеллектуальный указатель, работающийкак s t d : : sha red_pt r (см. раздел 4.2), но который не участвует в совместном владении ресурсом, на который указывает (друтими словами, указатель наподобие s t d : : sha red_pt r,который не влияет на счетчик ссылок объекта).
Эта разновидность интеллектуального указателя должна бороться с проблемой, неизвестной указателям s td : : sha red _pt r: возможностью того, что объект, на который он указывает, был уничтожен. Истинный интеллектуальный указатель в состоянии справиться с этой проблемой, отслеживая, когда он становится висячим, т.е. когда объект, на который он должен указывать, больше не существует.Именно таковым и является интеллектуальный указатель s t d : : weak_pt r.Вы можете удивиться, зачем может быть нужен указатель std : : weak_pt r. Вы, вероятно,удивитесь еще больше, когда познакомитесь с его API. Указатель std: : weak ptr не можетбыть ни разыменован, ни проверен на "нулевость': Дело в том, что std: : wea k_pt r не является автономным интеллектуальным указателем.
Это - дополнение к std : : sha red_ptr.Их взаимосвязь начинается с самого рождения: указатели std: : weak_pt r обычно создаются из указателей s td : : shared_pt r. Они указывают на то же место, что и инициализирующие их указатели std : : shared_pt r, но не влияют на счетчики ссылок объекта,на который указывают://auto spw =std : : make_shared<Widget> ( ) ; //11//После создания spw счетчикссылок указываемого Widgetравен 1 . ( 0 std: :make_sharedсм .
раздел 4 . 4 . )std: : weak_ytr<Widget> wpw ( spw) ; / / wpw указывает на тот же/ / Widge t , что и spw . Счетчик1 42Гла ва 4. Интеллектуальные указатели/ / ссЫJ1ок остается равным 1spw11 Счетчик ссЫJ1ок равен О , и1 1 Widget уничтожается .11 wpw становится висячимnullpt r ;О висячем s t d : : wea k_pt r говорят, что онэто непосредственно:if (wpw . expired ( ) )"просрочен(expired). Вы можете проверить.
// Если wpw не указывает на объект".Но чаше всего вам надо не просто проверить, не просрочен ли указатель s t d : : weak_pt r,но и, если он не просрочен (т.е. не является висячим), обратиться к объекту, на которыйон указывает. Это проще сказать, чем сделать. Поскольку у указателей s t d : : wea k_pt r нетопераций разыменования, нет и способа написать такой код. Даже если бы он был, разделение проверки и разыменования могло бы привести к состоянию гонки: между вызовомexpired и разыменованием другой поток мог бы переприсвоить или уничтожить последний std : : sha red_ptr, указывающий на объект, тем самым приводя к уничтожению самого объекта.
В этом случае ваше разыменование привело бы к неопределенному поведению.Что вам нужно - так это атомарная операция, которая проверяла бы просроченность указателя s t d : : wea k_pt r и, если он не просрочен, предоставляла вам доступк указываемому объекту. Это делается путем создания указателя s td : : shared_pt r изуказателя std : : wea k_pt r. Операция имеет две разновидности, в зависимости от того,что должно произойти в ситуации, когда s t d : : weak_pt r оказывается просроченнымпри попытке создания из него s t d : : s hared_pt r.