Какой тип указателя мне использовать, когда? - PullRequest
223 голосов
/ 03 января 2012

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

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

Ответы [ 4 ]

177 голосов
/ 03 января 2012

Совместное владение:
Принятые стандарты shared_ptr и weak_ptr в значительной степени аналогичны их аналогам Boost .Используйте их, когда вам нужно поделиться ресурсом и не знать, какой из них станет последним живым.Используйте weak_ptr для наблюдения общего ресурса, не влияя на его время жизни, чтобы не прерывать циклы.Циклы с shared_ptr обычно не должны происходить - два ресурса не могут владеть друг другом.

Обратите внимание, что Boost дополнительно предлагает shared_array, что может быть подходящей альтернативой shared_ptr<std::vector<T> const>.

Далее, Boost предлагает intrusive_ptr, которые являются легковесным решением, если ваш ресурс уже предлагает управление с подсчетом ссылок и вы хотите адаптировать его к принципу RAII.Этот стандарт не был принят.

Уникальное владение:
Повышение также имеет scoped_ptr, который не подлежит копированию и для которого вы можетене указывать удалитель.std::unique_ptr означает boost::scoped_ptr на стероидах и должно быть вашим выбором по умолчанию, когда вам нужен умный указатель .Он позволяет вам указать удалитель в аргументах шаблона и является подвижным , в отличие от boost::scoped_ptr.Он также полностью применим в контейнерах STL, если вы не используете операции, для которых нужны копируемые типы (очевидно).

Обратите внимание, что Boost имеет версию массива: scoped_array, который стандарт унифицирует, требуя std::unique_ptr<T[]> частичной специализации, которая будет delete[] указатель вместо delete его (с 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) обеспечивает лучшее решение. —конечная заметка ]

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

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

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

, является потенциальным условием гонки.

127 голосов
/ 03 января 2012

Решение о том, какой умный указатель использовать, является вопросом владения .Когда дело доходит до управления ресурсами, объект 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 для предыдущего, чтобы разорвать цикл.

19 голосов
/ 03 января 2012

Используйте 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.Тем не менее, они очень ниша и совершенно не нужны в общем случае.

6 голосов
/ 03 января 2012

Случаи использования unique_ptr:

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

Случаи использования shared_ptr:

  • Обмен объектами между потоками
  • Общий доступ к объектам

Случаи использования weak_ptr:

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

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

...