Умные указатели: кому принадлежит объект? [закрыто]

114

С ++ - это все о владении памятью - также известной как семантика владения .

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

В C ++ владение документировано типом, внутри которого заключен необработанный указатель, поэтому в хорошей (IMO) программе на C ++ очень редко ( редко , не никогда ) можно увидеть переданные необработанные указатели (поскольку исходные указатели не имеют предполагаемого владения, поэтому мы можем не указывать, кому принадлежит память, и, таким образом, без внимательного прочтения документации нельзя сказать, кто несет ответственность за владение).

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

Итак, вопрос:

  • С какой семантикой типа собственности сталкивались люди?
  • Какие стандартные классы используются для реализации этой семантики?
  • В каких ситуациях вы считаете их полезными?

Давайте сохраним 1 тип семантического владения для каждого ответа, чтобы можно было голосовать за них индивидуально.

Резюме:

Концептуально интеллектуальные указатели просты, и наивная реализация проста. Я видел много попыток реализации, но неизменно они ломаются, что не очевидно для случайного использования и примеров. Поэтому я рекомендую всегда использовать хорошо протестированные интеллектуальные указатели из библиотеки, а не использовать свои собственные. std::auto_ptrили один из интеллектуальных указателей Boost, кажется, покрывает все мои потребности.

std::auto_ptr<T>:

Объектом владеет один человек. Передача права собственности разрешена.

Использование: это позволяет вам определять интерфейсы, которые показывают явную передачу права собственности.

boost::scoped_ptr<T>

Объектом владеет один человек. Передача права собственности НЕ допускается.

Использование: используется для демонстрации явного владения. Объект будет уничтожен деструктором или при явном сбросе.

boost::shared_ptr<T>( std::tr1::shared_ptr<T>)

Множественное владение. Это простой указатель с подсчетом ссылок. Когда счетчик ссылок достигает нуля, объект уничтожается.

Использование: когда у объекта может быть несколько цветов, время жизни которых не может быть определено во время компиляции.

boost::weak_ptr<T>:

Используется shared_ptr<T>в ситуациях, когда может произойти цикл указателей.

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

Мартин Йорк
источник
14
?? Какой был вопрос?
Pacerier
9
Я просто хотел указать, что, поскольку этот вопрос был опубликован, auto_ptr устарел в пользу (теперь стандартизированного) unique_ptr
Хуана Кампа
In C++ ownership is documented by the type a RAW pointer is wrapped inside thus in a good (IMO) Можно ли это перефразировать? Я вообще этого не понимаю.
lolololol ol
@lololololol Вы сократили предложение пополам. In C++ ownership is documented by the type a RAW pointer is wrapped inside thus in a good C++ program it is very rare to see RAW pointers passed around, Указатели RAW не имеют семантики владения. Если вы не знаете владельца, вы не знаете, кто несет ответственность за удаление объекта. Существует несколько стандартных классов, которые используются для переноса указателей (std :: shared_ptr, std :: unique_ptr и т. Д.), Которые определяют право собственности и, следовательно, определить, кто отвечает за удаление указателя.
Мартин Йорк
1
В C ++ 11 + НЕ ИСПОЛЬЗУЙТЕ auto_ptr! Вместо этого используйте unique_ptr!
Вэл говорит: "Восстановите Монику"

Ответы:

20

Для меня эти 3 вида покрывают большинство моих потребностей:

shared_ptr - подсчет ссылок, освобождение, когда счетчик достигает нуля

weak_ptr- то же, что и выше, но это «раб» для a shared_ptr, не может освободить

auto_ptr- когда создание и освобождение происходит внутри одной и той же функции или когда объект должен всегда считаться единственным владельцем. Когда вы назначаете один указатель другому, второй «крадет» объект у первого.

У меня есть собственная реализация для них, но они также доступны в Boost.

Я по-прежнему передаю объекты по ссылке ( constкогда это возможно), в этом случае вызываемый метод должен предполагать, что объект жив только во время вызова.

Я использую другой вид указателя, который я называю hub_ptr . Это когда у вас есть объект, который должен быть доступен из вложенных в него объектов (обычно как виртуальный базовый класс). Это можно решить, передав weak_ptrим, но у него нет shared_ptrсамого себя. Поскольку он знает, что эти объекты не проживут дольше него, он передает им hub_ptr (это просто оболочка шаблона для обычного указателя).

Фабио Чеконелло
источник
2
Вместо того, чтобы создавать свой собственный класс указателя (hub_ptr), почему бы вам просто не передать * this этим объектам и позволить им сохранить его как ссылку? Поскольку вы даже признаете, что объекты будут уничтожены одновременно с классом-владельцем, я не понимаю смысла перепрыгивать через столько обручей.
Мишель
4
По сути, это контракт на дизайн, чтобы прояснить ситуацию. Когда дочерний объект получает hub_ptr, он знает, что указанный объект не будет уничтожен в течение жизни дочернего объекта, и не имеет на него права собственности. И содержащиеся, и контейнерные объекты подчиняются четкому набору правил. Если вы используете голый указатель, правила можно задокументировать, но компилятор и код не будут их применять.
Фабио Чеконелло
1
Также обратите внимание, что у вас может быть #ifdefs для определения типа hub_ptr на голый указатель в сборках выпуска, поэтому накладные расходы будут существовать только в сборке отладки.
Фабио Чеконелло
3
Обратите внимание, что документация Boost противоречит вашему описанию scoped_ptr. В нем говорится, что это так, noncopyableи что право собственности не может быть передано.
Алек Томас
3
@ Алек Томас, ты прав. Я думал об auto_ptr и написал scoped_ptr. Исправлено.
Fabio Ceconello 01
23

Простая модель C ++

В большинстве модулей, которые я видел, по умолчанию предполагалось, что получение указателей не получает права собственности. Фактически, функции / методы, отказывающиеся от владения указателем, были очень редкими и явно выражали этот факт в своей документации.

Эта модель предполагает, что пользователь является владельцем только того, что он / она явно выделяет . Все остальное автоматически удаляется (при выходе из области видимости или через RAII). Это C-подобная модель, дополненная тем фактом, что большинство указателей принадлежат объектам, которые освобождают их автоматически или при необходимости (в основном при уничтожении указанных объектов), и что продолжительность жизни объектов предсказуема (RAII - ваш друг, очередной раз).

В этой модели необработанные указатели свободно распространяются и в большинстве случаев не опасны (но если разработчик достаточно умен, он / она будет использовать вместо них ссылки, когда это возможно).

  • необработанные указатели
  • станд :: auto_ptr
  • повышение :: scoped_ptr

Умная заостренная модель C ++

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

  • повышение :: shared_ptr
  • повышение :: weak_ptr

Вывод

Независимо от моделей, которые я описываю, за исключением исключений, получение указателя не получает своего владения, и по-прежнему очень важно знать, кто кому принадлежит . Даже для кода C ++, интенсивно использующего ссылки и / или умные указатели.

paercebal
источник
10

У меня нет долевой собственности. Если да, убедитесь, что это только код, который вы не контролируете.

Это решает 100% проблем, поскольку заставляет вас понимать, как все взаимодействует.

MSN
источник
2
  • Совместная собственность
  • повышение :: shared_ptr

Когда ресурс совместно используется несколькими объектами. Boost shared_ptr использует подсчет ссылок, чтобы гарантировать, что ресурс не будет выделен, когда все будут завершены.

Мартин Йорк
источник
2

std::tr1::shared_ptr<Blah> довольно часто ваш лучший выбор.

Мэтт Крукшанк
источник
2
shared_ptr - самый распространенный. Но есть еще много чего. У каждого своя модель использования и хорошие и плохие места для подачи иска. Было бы неплохо еще немного описания.
Мартин Йорк
Если вы застряли со старым компилятором, boost :: shared_ptr <blah> - это то, на чем основан std :: tr1 :: shared_ptr <blah>. Это достаточно простой класс, и вы, вероятно, сможете скопировать его из Boost и использовать, даже если ваш компилятор не поддерживается последней версией Boost.
Бранан,
2

Из boost есть также библиотека контейнеров указателей . Они немного эффективнее и проще в использовании, чем стандартный контейнер интеллектуальных указателей, если вы будете использовать объекты только в контексте их контейнера.

В Windows есть указатели COM (IUnknown, IDispatch и другие) и различные интеллектуальные указатели для их обработки (например, CComPtr ATL и интеллектуальные указатели, автоматически сгенерированные оператором import в Visual Studio на основе класса _com_ptr ).

Райан Гинстром
источник
1
  • Один владелец
  • повышение :: scoped_ptr

Когда вам нужно динамически распределять память, но вы хотите, чтобы она освобождалась в каждой точке выхода из блока.

Я считаю это полезным, так как его можно легко переустановить и отпустить, даже не беспокоясь об утечке.

Pieter
источник
1

Я не думаю, что когда-либо имел возможность совместно владеть своим дизайном. Фактически, я только что могу придумать, что это единственный допустимый случай, это шаблон наилегчайшего веса.

Неманья Трифунович
источник
1

yasper :: ptr - это легкая альтернатива boost :: shared_ptr. Он хорошо работает в моем (пока) небольшом проекте.

На веб-странице http://yasper.sourceforge.net/ это описано следующим образом:

Зачем писать еще один умный указатель на C ++? Уже существует несколько высококачественных реализаций интеллектуальных указателей для C ++, в первую очередь пантеон указателей Boost и SmartPtr от Loki. Для хорошего сравнения реализаций интеллектуальных указателей и когда их использование целесообразно, прочтите книгу Херба Саттера Новый C ++: интеллектуальные (er) указатели. В отличие от обширных возможностей других библиотек, Yasper - это узконаправленный указатель подсчета ссылок. Это близко соответствует политикам shared_ptr Boost и RefCounted / AllowConversion от Loki. Yasper позволяет программистам на C ++ забыть об управлении памятью, не вводя большие зависимости Boost или не узнавая о сложных шаблонах политик Loki. философия

* small (contained in single header)
* simple (nothing fancy in the code, easy to understand)
* maximum compatibility (drop in replacement for dumb pointers)

Последний пункт может быть опасным, поскольку yasper допускает рискованные (но полезные) действия (такие как присвоение необработанных указателей и ручное освобождение), запрещенные другими реализациями. Будьте осторожны, используйте эти функции только в том случае, если знаете, что делаете!

Эрнан
источник
1

Существует еще одна часто используемая форма единственного передаваемого владельца, и она предпочтительнее, auto_ptrпоскольку позволяет избежать проблем, вызванных auto_ptrбезумным искажением семантики присваивания.

Я говорю не о чем другом swap. Любой тип с подходящей swapфункцией можно представить себе как интеллектуальную ссылку на некоторый контент, которым он владеет до тех пор, пока право собственности не будет передано другому экземпляру того же типа путем их замены. Каждый экземпляр сохраняет свою идентичность, но привязывается к новому контенту. Это похоже на безопасную повторную привязку.

(Это умная ссылка, а не умный указатель, потому что вам не нужно явно разыменовать его, чтобы получить доступ к содержимому.)

Это означает, что auto_ptr становится менее необходимым - он нужен только для заполнения пробелов, в которых типы не имеют хорошей swapфункции. Но все стандартные контейнеры работают.

Дэниел Эрвикер
источник
Возможно, это станет менее необходимым (я бы сказал, что scoped_ptr делает его менее необходимым, чем это), но это никуда не денется. Наличие функции подкачки вам совсем не поможет, если вы выделяете что-то в куче, и кто-то бросает, прежде чем вы его удалите, или вы просто забываете.
Мишель
Это именно то, что я сказал в последнем абзаце.
Дэниел Эрвикер,
0
  • Один владелец: также известен как удаление при копировании
  • станд :: auto_ptr

Когда создатель объекта хочет явно передать право собственности кому-то другому. Это также способ документирования в коде, который я передаю вам, и я больше не отслеживаю его, поэтому обязательно удалите его, когда закончите.

Мартин Йорк
источник