Какой тип указателя мне использовать, когда?
Итак, в последний раз я писал C++ для жизни, std::auto_ptr
было все доступное в std lib, и boost::shared_ptr
был весь гнев. Я действительно никогда не смотрел на другие умные типы указателей. Я понимаю, что C++11 теперь предоставляет некоторые из типов boost, но не все.
Так есть ли у кого-то простой алгоритм, чтобы определить, когда использовать какой умный указатель? Желательно включать советы, касающиеся тупых указателей (необработанных указателей, таких как T*
) и остальные умные указатели наддува. (Что-то вроде этого было бы здорово).
4 ответа
Совместная собственность:
shared_ptr
а также weak_ptr
принятый стандарт в значительной степени совпадает с их аналогами Boost. Используйте их, когда вам нужно поделиться ресурсом и не знать, какой из них будет последним живым. использование weak_ptr
наблюдать за общим ресурсом, не влияя на его время жизни, чтобы не прерывать циклы. Циклы с shared_ptr
обычно не должно происходить - два ресурса не могут владеть друг другом.
Обратите внимание, что Boost дополнительно предлагает shared_array
, которая может быть подходящей альтернативой shared_ptr<std::vector<T> const>
,
Далее Boost предлагает intrusive_ptr
, которые являются легковесным решением, если ваш ресурс уже предлагает подсчет ссылок, и вы хотите адаптировать его к принципу RAII. Этот не был принят стандартом.
Уникальная собственность:
Boost также имеет scoped_ptr
, который не подлежит копированию и для которого нельзя указать удалитель. std::unique_ptr
является boost::scoped_ptr
на стероидах и должен быть вашим выбором по умолчанию, когда вам нужен умный указатель. Он позволяет вам указывать удалитель в аргументах шаблона и является подвижным, в отличие от boost::scoped_ptr
, Он также полностью применим в контейнерах STL, если вы не используете операции, для которых нужны копируемые типы (очевидно).
Обратите внимание, что Boost имеет версию массива: scoped_array
, который стандарт унифицирован, требуя std::unique_ptr<T[]>
частичная специализация, которая будет delete[]
указатель вместо delete
это (с default_delete
р). std::unique_ptr<T[]>
также предлагает operator[]
вместо operator*
а также operator->
,
Обратите внимание, что std::auto_ptr
все еще в стандарте, но это устарело. §D.10 [depr.auto.ptr]
Шаблон класса
auto_ptr
устарела. [ Примечание: шаблон классаunique_ptr
(20.7.1) обеспечивает лучшее решение. —Конечная записка ]
Нет собственности:
Используйте немые указатели (необработанные указатели) или ссылки для не владеющих ссылками на ресурсы, и когда вы знаете, что ресурс переживет ссылающийся объект / область. Предпочитайте ссылки и используйте необработанные указатели, когда вам нужно обнулять или сбрасывать.
Если вы хотите не принадлежащую ссылку на ресурс, но не знаете, будет ли ресурс переживать объект, который на него ссылается, упакуйте ресурс в shared_ptr
и использовать weak_ptr
- вы можете проверить, если родитель shared_ptr
жив с lock
, который вернет shared_ptr
это ненулевое значение, если ресурс все еще существует. Если вы хотите проверить, мертв ли ресурс, используйте expired
, Оба могут звучать одинаково, но сильно отличаются друг от друга в условиях одновременного выполнения, так как expired
только гарантирует его возвращаемое значение для этого единственного утверждения. Казалось бы, невинный тест, как
if(!wptr.expired())
something_assuming_the_resource_is_still_alive();
это потенциальное состояние гонки.
Решение о том, какой умный указатель использовать, является вопросом владения. Когда дело доходит до управления ресурсами, объект A владеет объектом B, если он контролирует время жизни объекта B. Например, переменные-члены принадлежат их соответствующим объектам, потому что время жизни переменных-членов связано с временем жизни объекта. Вы выбираете умные указатели в зависимости от того, как принадлежит объект.
Обратите внимание, что владение программной системой отделено от владения, как мы думаем, вне программного обеспечения. Например, человек может "владеть" своим домом, но это не обязательно означает, что Person
Объект имеет контроль над временем жизни House
объект. Сопоставление этих концепций реального мира с концепциями программного обеспечения - верный способ запрограммировать себя в дыру.
Если вы являетесь единоличным владельцем объекта, используйте std::unique_ptr<T>
,
Если вы разделили право собственности на объект...
- Если в собственности нет циклов, используйте std::shared_ptr<T>
,
- Если есть циклы, определите "направление" и используйте std::shared_ptr<T>
в одном направлении и std::weak_ptr<T>
в другом.
Если объект принадлежит вам, но существует вероятность отсутствия владельца, используйте обычные указатели T*
(например, родительские указатели).
Если объект принадлежит вам (или иным образом имеет гарантированное существование), используйте ссылки T&
,
Предостережение: знайте о стоимости умных указателей. В средах с ограниченной памятью или производительностью может быть полезно просто использовать обычные указатели с более ручной схемой управления памятью.
Расходы:
- Если у вас есть пользовательское средство удаления (например, вы используете пулы распределения), это повлечет за собой накладные расходы на указатель, которых можно легко избежать путем ручного удаления.
std::shared_ptr
имеет накладные расходы на увеличение счетчика ссылок на копию, плюс уменьшение на уничтожение, за которым следует проверка 0 счетчика с удалением удерживаемого объекта. В зависимости от реализации, это может раздуть ваш код и вызвать проблемы с производительностью.- Время компиляции. Как и во всех шаблонах, умные указатели негативно влияют на время компиляции.
Примеры:
struct BinaryTree
{
Tree* m_parent;
std::unique_ptr<BinaryTree> m_children[2]; // or use std::array...
};
Бинарное дерево не владеет своим родителем, но существование дерева подразумевает существование его родителя (или nullptr
для корня), так что используется нормальный указатель. Бинарное дерево (с семантикой значения) имеет единоличное владение своими дочерними элементами, поэтому std::unique_ptr
,
struct ListNode
{
std::shared_ptr<ListNode> m_next;
std::weak_ptr<ListNode> m_prev;
};
Здесь узлу списка принадлежат его следующий и предыдущий списки, поэтому мы определяем направление и используем shared_ptr
для следующего и weak_ptr
для прервать цикл.
Использование unique_ptr<T>
все время, кроме случаев, когда вам нужен подсчет ссылок, в этом случае используйте shared_ptr<T>
(и для очень редких случаев, weak_ptr<T>
для предотвращения референтных циклов). Практически в каждом случае передаваемое уникальное право собственности просто отлично.
Необработанные указатели: хорошо, только если вам нужны ковариантные возвраты, не имеющие указания, которые могут произойти. В противном случае они не очень полезны.
Указатели массива: unique_ptr
имеет специализацию для T[]
который автоматически вызывает delete[]
на результат, так что вы можете смело делать unique_ptr<int[]> p(new int[42]);
например. shared_ptr
вам все равно понадобится пользовательский удалитель, но вам не понадобится специализированный указатель общего или уникального массива. Конечно, такие вещи обычно лучше заменить std::vector
тем не мение. к несчастью shared_ptr
не предоставляет функцию доступа к массиву, поэтому вам все равно придется вручную вызывать get()
, но unique_ptr<T[]>
обеспечивает operator[]
вместо operator*
а также operator->
, В любом случае, вы должны проверить себя самостоятельно. Это делает shared_ptr
немного менее удобный для пользователя, хотя возможно общее преимущество и отсутствие зависимости Boost делает unique_ptr
а также shared_ptr
снова победители.
Указатели по областям: не имеют значения unique_ptr
, как auto_ptr
,
Там действительно больше ничего нет. В C++03 без семантики перемещения эта ситуация была очень сложной, но в C++11 совет очень прост.
Есть все еще использование для других умных указателей, таких как intrusive_ptr
или же interprocess_ptr
, Тем не менее, они очень нишевые и совершенно ненужные в общем случае.
Случаи, когда использовать unique_ptr
:
- Фабричные методы
- Члены, которые являются указателями (включая pimpl)
- Хранение указателей в контейнерах stl (чтобы избежать перемещений)
- Использование больших локальных динамических объектов
Случаи, когда использовать shared_ptr
:
- Совместное использование объектов в потоках
- Общий доступ к объектам
Случаи, когда использовать weak_ptr
:
- Большая карта, которая служит общей ссылкой (например, карта всех открытых сокетов)
Не стесняйтесь редактировать и добавлять больше