Абстрактное описание
Некоторые люди говорят, что shared_ptr
- это «умный указатель счетчика ссылок».Я не думаю, что это правильный способ взглянуть на это.
На самом деле shared_ptr
- это все (неисключительное) владение : все shared_ptr
, которые являются копиямиshared_ptr
инициализируется указателем p
являются владельцами .
shared_ptr
отслеживает набор владельцев , чтобы гарантировать, что:
- , когда набор владельцев не пуст,
delete p
не вызывается - , когда набор владельцев становится пустым,
delete p
(или копия D
функтора уничтожения).) вызывается немедленно;
Конечно, чтобы определить, когда набор владельцев становится пустым, shared_ptr
нужен только счетчик.Абстрактное описание немного проще для понимания.
Возможные методы реализации
Чтобы отслеживать количество владельцев, счетчик - это не только самый очевидный подход, но и относительно очевидный способсделать потокобезопасным с помощью атомарного сравнения и изменения.
Чтобы отслеживать всех владельцев, связанный список владельцев является не только очевидным решением, но и простым способом избежать необходимости выделять какие-либопамять для каждого набора владельцев.Проблема в том, что нелегко сделать такой подход эффективно потокобезопасным (все можно сделать потокобезопасным с помощью глобальной блокировки, что противоречит самой идее параллелизма).
В случае многопоточной реализации
С одной стороны, у нас есть небольшое выделение памяти фиксированного размера (если не используется функция уничтожения), которое очень легко оптимизировать, и простые целочисленные атомарные операции.
С другой стороны, существует дорогостоящая и сложная обработка связанных списков;и если необходим мьютекс для каждого владельца (как мне кажется), стоимость выделения памяти возвращается, и в этот момент мы можем просто заменить мьютекс на счетчик!
О нескольких возможных реализациях
Сколько раз я читал, что возможны многие реализации для "стандартного" класса?
Кто никогда не слышал эту фантазию о том, что сложный класс может быть реализован как полярные координаты?Это идиотизм, как мы все знаем.комплекс должен использовать декартовы координаты.Если полярные координаты предпочтительны, необходимо создать другой класс.Нет никакого способа, которым полярный комплексный класс будет использоваться для замены обычного комплексного класса.
То же самое для (нестандартного) строкового класса: для строкового класса нет причин.быть внутренним NUL-завершением и не хранить длину как целое число, просто для забавы и неэффективности повторного вызова strlen
.
Теперь мы знаем, что проектирование std::string
для допуска COW было плохой идеей, котораяпричина необычной семантики аннулирования константных итераторов.
std::vector
теперь гарантированно будет непрерывной.
Конец фантазии
В какой-то момент фантазиятам, где стандартные классы имеют много существенных отличий, разумные реализации должны быть отброшены.Стандартные классы являются примитивными строительными блоками;не только они должны быть очень эффективными, они должны иметь предсказуемую эффективность.
Программист должен иметь возможность делать переносимые предположения об относительной скорости основных операций.Сложный класс бесполезен для серьезного сокращения чисел, если даже простейшее сложение превращается в кучу трансцендентных вычислений.Если не гарантируется, что у класса строк будет очень быстрое копирование с помощью совместного использования данных, программист должен будет минимизировать количество копий строк.
Разработчик может выбирать различные методы реализации только тогда, когда это не так.сделать обычную дешевую операцию чрезвычайно дорогой (для сравнения).
Для многих классов это означает, что существует ровно одна жизнеспособная стратегия реализации, иногда с несколькими степенями свободы (например, размером блока).в std::deque
).