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

228

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

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

SBI
источник
Смотрите также станд :: auto_ptr к станд :: unique_ptr
Мартин - Йорк
1
Я действительно надеюсь, что кто-то придумает хорошую удобную блок-схему, такую ​​как блок-схема выбора STL .
Alok Save
1
@Als: О, это действительно хорошо! Я часто задавал вопросы об этом.
2012 года
6
@Deduplicator Это даже близко не быть дубликатом. Связанный вопрос говорит : «Когда я должен использовать в смарт - указатель» и этот вопрос «Когда я использую эти умные указатели?» то есть этот классифицирует различное использование стандартных интеллектуальных указателей. Связанный вопрос не делает этого. Разница, на первый взгляд, маленькая, но большая.
Раппц

Ответы:

183

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

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

Далее, Повышение предложение 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_deleter). 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();

это потенциальное состояние гонки.

Xeo
источник
1
В случае отсутствия владения, вам, вероятно, следует предпочесть ссылки на указатели, если вам не нужны владение и возможность сброса, когда ссылки не будут обрезать его, даже тогда вы можете подумать о том, чтобы переписать исходный объект как shared_ptrуказатель a, а не принадлежащий указатель - a weak_ptr...
Дэвид Родригес - dribeas
2
Я имел в виду не ссылку на указатель , а ссылку вместо указателя. Если владения нет, если вам не требуется возможность сброса (или обнуляемости, но обнуляемость без возможности сброса будет весьма ограничена), вы можете использовать простую ссылку, а не указатель в первую очередь.
Дэвид Родригес - dribeas
1
@ Дэвид: Ах, я вижу. :) Да, ссылки для этого неплохие, я лично в таких случаях тоже предпочитаю. Я добавлю их.
Xeo
1
@Xeo: shared_array<T>альтернатива shared_ptr<T[]>не shared_ptr<vector<T>>: она не может расти.
Р. Мартиньо Фернандес
1
@GregroyCurrie: Это ... именно то, что я написал? Я сказал, что это пример потенциального состояния гонки.
Xeo
127

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

Петр Александр
источник
3
Для примера бинарного дерева некоторые люди предложили бы использовать shared_ptr<BinaryTree>для детей и weak_ptr<BinaryTree>для родительских отношений.
Дэвид Родригес - dribeas
@ DavidRodríguez-dribeas: Это зависит от того, имеет ли Дерево семантику значения или нет. Если люди будут ссылаться на ваше дерево извне, даже если дерево источника уничтожено, тогда да, лучше использовать комбинированный / слабый указатель.
Петр Александр
Если объект принадлежит вам и гарантированно существует, то почему бы не ссылка.
Мартин Йорк,
1
Если вы используете ссылку, вы никогда не сможете изменить родителя, что может или не может помешать дизайну. Для балансировки деревьев это будет мешать.
Mooing Duck
3
+1, но вы должны добавить определение «владение» в первой строке. Мне часто приходится четко заявлять, что речь идет о жизни и смерти объекта, а не о владении в более конкретном доменном смысле.
12:30
19

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

щенок
источник
Кроме того, сырые указатели для итерации. И для буферов выходных параметров, где буфер принадлежит вызывающей стороне.
Бен Фойгт
Хм, как я это понял, это ситуации, которые являются одновременно ковариантными возвращениями и не принадлежащими. Переписать может быть хорошо, если вы имели в виду объединение, а не пересечение. Я бы также сказал, что итерация заслуживает особого упоминания.
Бен Фойгт
2
std::unique_ptr<T[]>обеспечивает operator[]вместо operator*и operator->. Это правда, что вы все еще должны сделать обязательную проверку, хотя.
Xeo
8

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

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

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

  • Совместное использование объектов в потоках
  • Общий доступ к объектам

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

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

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

Lalaland
источник
На самом деле мне больше нравится ваш ответ, когда вы даете сценарии.
Николас Хамфри