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

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

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

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

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

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

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

Давайте сохраним 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> в ситуациях, когда может произойти цикл указателей.

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

?? Какой был вопрос?

Pacerier 18.05.2015 13:21

Я просто хотел указать, что, поскольку этот вопрос был опубликован, auto_ptr устарел в пользу (теперь стандартизированного) unique_ptr

Juan Campa 13.05.2016 03:30

In C++ ownership is documented by the type a RAW pointer is wrapped inside thus in a good (IMO) Можно это перефразировать? Я вообще этого не понимаю.

lolololol ol 20.12.2017 18:10

@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 и т. д.), Которые определяют право собственности и, следовательно, определить, кто отвечает за удаление указателя.

Martin York 20.12.2017 20:49

В C++ 11 + НЕ ИСПОЛЬЗУЙТЕ auto_ptr! Вместо этого используйте unique_ptr!

val says Reinstate Monica 12.07.2018 19:03
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
115
5
56 226
11
Перейти к ответу Данный вопрос помечен как решенный

Ответы 11

  • Один владелец: также известен как удаление при копировании
  • std :: auto_ptr

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

  • Совместная собственность
  • boost :: shared_ptr

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

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

shared_ptr - самый распространенный. Но есть еще много чего. У каждого своя модель использования и хорошие и плохие места для подачи иска. Было бы неплохо еще немного описания.

Martin York 18.09.2008 20:43

Если вы застряли со старым компилятором, boost :: shared_ptr <blah> - это то, на чем основан std :: tr1 :: shared_ptr <blah>. Это достаточно простой класс, и вы, вероятно, сможете скопировать его из Boost и использовать, даже если ваш компилятор не поддерживается последней версией Boost.

Branan 18.09.2008 20:55
  • Один владелец
  • boost :: scoped_ptr

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

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

Ответ принят как подходящий

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

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

weak_ptr - то же, что и выше, но это "подчиненный" для shared_ptr, не может освободить

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

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

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

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

Вместо того, чтобы создавать свой собственный класс указателя (hub_ptr), почему бы вам просто не передать * this этим объектам и позволить им сохранить его как ссылку? Поскольку вы даже признаете, что объекты будут уничтожены одновременно с классом-владельцем, я не понимаю смысла перепрыгивать через столько обручей.

Michel 08.05.2009 19:59

По сути, это контракт на дизайн, чтобы прояснить ситуацию. Когда дочерний объект получает hub_ptr, он знает, что указанный объект не будет уничтожен в течение жизни дочернего объекта, и не имеет на него права собственности. И содержащиеся, и контейнерные объекты подчиняются четкому набору правил. Если вы используете голый указатель, правила можно задокументировать, но компилятор и код не будут их применять.

Fabio Ceconello 19.05.2009 03:07

Также обратите внимание, что у вас может быть #ifdefs для определения типа hub_ptr на голый указатель в сборках выпуска, поэтому накладные расходы будут существовать только в сборке отладки.

Fabio Ceconello 19.05.2009 03:09

Обратите внимание, что Документация Boost противоречит вашему описанию scoped_ptr. В нем указано, что это noncopyable и что право собственности не может быть передано.

Alec Thomas 31.05.2012 23:15

@ Алек Томас, ты прав. Я думал об auto_ptr и написал scoped_ptr. Исправлено.

Fabio Ceconello 01.06.2012 17:31

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

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

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

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

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

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

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

  • необработанные указатели
  • std :: auto_ptr
  • boost :: scoped_ptr

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

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

  • boost :: shared_ptr
  • boost :: weak_ptr

Вывод

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

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

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

Why write another C++ smart pointer? There already exist several high quality smart pointer implementations for C++, most prominently the Boost pointer pantheon and Loki's SmartPtr. For a good comparison of smart pointer implementations and when their use is appropriate please read Herb Sutter's The New C++: Smart(er) Pointers. In contrast with the expansive features of other libraries, Yasper is a narrowly focused reference counting pointer. It corresponds closely with Boost's shared_ptr and Loki's RefCounted/AllowConversion policies. Yasper allows C++ programmers to forget about memory management without introducing the Boost's large dependencies or having to learn about Loki's complicated policy templates. Philosophy

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

The last point can be dangerous, since yasper permits risky (yet useful) actions (such as assignment to raw pointers and manual release) disallowed by other implementations. Be careful, only use those features if you know what you're doing!

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

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

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

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

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

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

Возможно, это станет менее необходимым (я бы сказал, что scoped_ptr делает это менее необходимым, чем это), но это никуда не денется. Наличие функции подкачки вам совсем не поможет, если вы выделяете что-то в куче, и кто-то бросает перед удалением, или вы просто забываете.

Michel 08.05.2009 20:03

Это именно то, что я сказал в последнем абзаце.

Daniel Earwicker 08.05.2009 22:18

Другие вопросы по теме