Подтвердить что ты не робот

Как работают общие указатели?

Как общие указатели знают, сколько указателей указывают на этот объект? (shared_ptr, в этом случае)

4b9b3361

Ответ 1

В принципе, shared_ptr имеет два указателя: указатель на общий объект и указатель на структуру, содержащую два подсчета ссылок: один для "сильных ссылок" или ссылок, имеющих право собственности, и один для "слабых ссылок", или ссылки, которые не имеют права собственности.

При копировании shared_ptr конструктор копирования увеличивает количество ссылок. Когда вы уничтожаете shared_ptr, деструктор уменьшает счетчик ссылок и проверяет, равен ли счетчик ссылок нулю; если это так, деструктор удаляет общий объект, потому что no shared_ptr указывает на него больше.

Слабый подсчет ссылок используется для поддержки weak_ptr; в основном, в любое время, когда a weak_ptr создается из shared_ptr, подсчет слабой ссылки увеличивается, и в любой момент времени уничтожается слабый счетчик ссылок уменьшается. Пока либо сильный счетчик ссылок, либо слабый счетчик ссылок больше нуля, структура ссылочного счета не будет уничтожена.

Эффективно, пока сильный счетчик ссылок больше нуля, общий объект не будет удален. Пока сильный счетчик ссылок или слабый счетчик ссылок не равен нулю, структура ссылочного счета не будет удалена.

Ответ 2

Я вообще согласен с ответом Джеймса Макнеллиса. Однако есть еще один момент, который следует упомянуть.

Как вы знаете, shared_ptr<T> также может использоваться, когда тип T не полностью определен.

То есть:

class AbraCadabra;

boost::shared_ptr<AbraCadabra> myPtr;
// ...

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

Это достигается с помощью следующего трюка: shared_ptr фактически состоит из следующего:

  • Непрозрачный указатель на объект
  • Общие счетчики ссылок (описание Джеймса Макнеллиса)
  • Указатель на выделенный factory, который знает, как уничтожить ваш объект.

Вышеупомянутый factory является вспомогательным объектом с единственной виртуальной функцией, которая должна правильно удалить ваш объект.

Этот factory фактически создается, когда вы назначаете значение для вашего общего указателя.

То есть следующий код

AbraCadabra* pObj = /* get it from somewhere */;
myPtr.reset(pObj);

Здесь размещается этот factory. Примечание. Функция reset на самом деле является шаблоном. Он фактически создает factory для указанного типа (тип объекта, переданного в качестве параметра). Здесь ваш тип должен быть полностью определен. То есть, если он еще не определен, вы получите ошибку компиляции.

Обратите внимание: если вы фактически создаете объект производного типа (производный от AbraCadabra) и назначаете его shared_ptr - он будет удален корректно, даже если ваш деструктор не является виртуальным. shared_ptr всегда удаляет объект в соответствии с типом, который видит в функции reset.

Итак, shared_ptr - довольно сложный вариант умного указателя. Это дает потрясающую гибкость . Однако вы должны знать, что эта гибкость достигается при цене крайне плохой производительности по сравнению с другими возможными реализациями интеллектуального указателя.

С другой стороны - есть так называемые "интрузивные" умные указатели. Они не обладают такой гибкостью, однако, наоборот, они дают лучшую производительность.

Плюсы shared_ptr по сравнению с интрузивными интеллектуальными указателями:

  • Очень гибкое использование. Определить только инкапсулированный тип нужно только при назначении shared_ptr. Это очень важно для больших проектов, значительно уменьшает зависимость.
  • В инкапсулированном типе не должен быть виртуальный деструктор, все же полиморфные типы будут удалены правильно.
  • Может использоваться со слабыми указателями.

Минусы shared_ptr по сравнению с интрузивными интеллектуальными указателями:

  • Очень варварская производительность и растрата памяти кучи. При назначении выделяет еще 2 объекта: счетчики ссылок, плюс factory (трата памяти, медленная). Однако это происходит только на reset. Когда один shared_ptr присваивается другому - ничего больше не выделяется.
  • Вышеупомянутое может вызвать исключение. (условие отсутствия памяти). Напротив, интуитивные интеллектуальные указатели могут никогда не бросать (кроме исключений процесса, связанных с недопустимым доступом к памяти, переполнением стека и т.д.).
  • Удаление вашего объекта также медленное: нужно освободить еще две структуры.
  • При работе с интрузивными интеллектуальными указателями вы можете свободно смешивать интеллектуальные указатели с исходными. Это нормально, потому что фактический подсчет ссылок находится внутри самого объекта, который является одиночным. В отличие от shared_ptr вы можете не смешивать с необработанными указателями.

    AbraCadabra * pObj =/* получить его откуда-то */; myPtr.reset(PObj); //... pObj = myPtr.get(); boost:: shared_ptr myPtr2 (pObj);//oops

Вышеуказанное выйдет из строя.

Ответ 3

Существует не менее трех известных механизмов.

Внешние счетчики

Когда первый общий указатель на объект создается, создается отдельный объект подсчета ссылок и инициализируется до 1. Когда копия указана, счетчик ссылок увеличивается; когда указатель уничтожен, он уменьшается. Назначение указателя увеличивает один счетчик и уменьшает другое (в этом порядке, иначе саморазделение ptr=ptr будет прерываться). Если счетчик ссылок обращается в нуль, больше указателей не существует и объект удаляется.

Внутренние счетчики

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

Циркулярные ссылки

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

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

Варианты

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

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

Ответ 4

Они содержат внутренний счетчик ссылок, который увеличивается в экземпляре конструктора/назначения экземпляра shared_ptr и уменьшается в деструкторе. Когда счетчик достигнет нуля, удерживаемый указатель будет удален.

Здесь представлена ​​библиотека Boost документация для интеллектуальных указателей. Я думаю, что реализация TR1 в основном совпадает с boost::shared_ptr.

Ответ 5

"Общий указатель - это умный указатель (объект С++ с перегруженным оператором *() и operator → ()), который хранит указатель на объект и указатель на общий счет ссылок. Каждый раз, когда копия умного указатель создается с использованием конструктора копирования, счетчик ссылок увеличивается. Когда общий указатель уничтожается, счетчик ссылок для его объекта уменьшается. Общие указатели, построенные из исходных указателей, первоначально имеют счетчик ссылок 1. Когда счетчик ссылок достигает 0, заостренный объект уничтожается, а память, которую он занимает, освобождается. Вам не нужно явно уничтожать объекты: это будет сделано автоматически, когда работает последний деструктор указателя." Из здесь.