Какой тип указателя я использую, когда?

Хорошо, поэтому в последний раз, когда я писал С++ для жизни, std::auto_ptr был доступным для всех std lib, а boost::shared_ptr - все это ярость. Я никогда не смотрел на другие улучшенные форсированные типы указателей. Я понимаю, что С++ 11 теперь предоставляет некоторые из типов boost, которые возникли, но не все из них.

У кого-то есть простой алгоритм, чтобы определить, когда использовать этот умный указатель? Предпочтительно включать советы о немых указателях (необработанные указатели, такие как T*) и остальные интеллектуальные указатели повышения. (Что-то вроде этого было бы здорово).

Ответы

Ответ 1

Совместное владение:
Принятые стандарты shared_ptr и weak_ptr в значительной степени совпадают с их Boost counterparts. Используйте их, когда вам нужно поделиться ресурсом и не знаете, какой из них будет последним, чтобы быть живым. Используйте 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 ing (с default_delete r). std::unique_ptr<T[]> также предлагает operator[] вместо operator* и operator->.

Обратите внимание, что std::auto_ptr все еще находится в стандарте, но он устарел. §D.10 [depr.auto.ptr]

Шаблон класса auto_ptr устарел. [Примечание. Шаблон класса unique_ptr (20.7.1) обеспечивает лучшее решение. -end note]

Нет собственности:
Используйте немые указатели (необработанные указатели) или ссылки для ссылок, не относящихся к владельцам, и когда вы знаете, что ресурс перейдет в ссылочный объект/область. Предпочитайте ссылки и используйте необработанные указатели, если вам нужна нулеустойчивость или возможность переустановки.

Если вам нужна ссылка на ресурс, не связанная с владельцем, но вы не знаете, перейдет ли ресурс объекта, который его ссылается, упакуйте ресурс в shared_ptr и используйте weak_ptr - вы можете проверить если родительский shared_ptr жив с lock, который вернет shared_ptr, который не равен null, если ресурс все еще существует. Если вы хотите проверить, мертв ли ​​ресурс, используйте expired. Оба могут казаться похожими, но сильно отличаются друг от друга во время параллельного выполнения, поскольку expired гарантирует только его возвращаемое значение для этого единственного оператора. По-видимому, невинный тест вроде

if(!wptr.expired())
  something_assuming_the_resource_is_still_alive();

- условие потенциальной расы.

Ответ 2

Решение о том, какой умный указатель использовать, является вопросом собственности. Когда дело доходит до управления ресурсами, объект 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 для root), поэтому он использует обычный указатель. Бинарное дерево (с семантикой значений) имеет единоличное право собственности на своих детей, поэтому это std::unique_ptr.

struct ListNode
{
    std::shared_ptr<ListNode> m_next;
    std::weak_ptr<ListNode> m_prev;
};

Здесь список node имеет свой следующий и предыдущий списки, поэтому мы определяем направление и используем shared_ptr для следующего и weak_ptr для прерывания цикла.

Ответ 3

Используйте 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.

Там действительно ничего более. В С++ 03 без семантики перемещения эта ситуация была очень сложной, но в С++ 11 совет очень прост.

По-прежнему используются другие интеллектуальные указатели, например intrusive_ptr или interprocess_ptr. Тем не менее, они очень ниши и совершенно ненужны в общем случае.

Ответ 4

Случаи, когда следует использовать unique_ptr:

  • Factory методы
  • Члены, которые являются указателями (включая pimpl)
  • Сохранение указателей в stl-контейнерах (чтобы избежать ходов)
  • Использование больших локальных динамических объектов

Случаи, когда следует использовать shared_ptr:

  • Обмен объектами по потокам
  • Связывание или захват указателей (использование lambda или std:: bind)
  • Обмен объектами вообще
  • Пользовательские удаления

Случаи, когда следует использовать weak_ptr:

  • Большая карта, которая действует как общая ссылка (например, карта всех открытых сокетов)

Не стесняйтесь редактировать и добавлять больше

Ответ 5

Для обсуждения указателей я бы порекомендовал некоторые из лучших сеансов на С++, которые я когда-либо наблюдал у Шона Пиана:

https://channel9.msdn.com/Events/GoingNative/2013/Cpp-Seasoning https://channel9.msdn.com/Events/GoingNative/2013/Inheritance-Is-The-Base-Class-of-Evil

Третий пункт первого разговора (мин. 48:50) говорит все: не используйте (почти) любой указатель! Я знаю, что это шокирует, но я думаю, что Шон действительно хорош здесь...

Во втором разговоре Шон объясняет свои полиморфные контейнеры без указателей базового класса в глубине... очень приятно!

Наслаждайтесь!