Передача unique_ptr функциям

Я пытаюсь «модернизировать» какой-то существующий код.

  • У меня есть класс, который в настоящее время имеет переменную-член «Device * device_».
  • Он использует new для создания экземпляра в некотором коде инициализации и имеет "delete device_" в деструкторе.
  • Функции-члены этого класса вызывают многие другие функции, которые принимают Device * в качестве параметра.

Это работает хорошо, но, чтобы «модернизировать» мой код, я подумал, что мне следует изменить переменную, определив ее как "std::unique_ptr<Device> device_", и удалить явный вызов удаления, что делает код более безопасным и в целом лучше.

У меня такой вопрос -

  • Как мне затем передать переменную device _ всем функциям, которым она нужна в качестве параметра?

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

Или я могу изменить каждую функцию так, чтобы вместо параметра типа «Устройство *» теперь принимался параметр типа «std :: unique_ptr &». Что (для меня) несколько запутывает прототипы функций и затрудняет их чтение.

Что для этого лучше всего? Пропустил ли я какие-то другие варианты?


person jcoder    schedule 14.03.2012    source источник
comment
Почему это изменение сделало код более безопасным и в целом лучше? Судя по вашему описанию, я бы сказал, что все как раз наоборот.   -  person James Kanze    schedule 14.03.2012
comment
@James Kanze Я согласен, в этом случае unique_ptr, похоже, ничего не покупает.   -  person juanchopanza    schedule 14.03.2012
comment
Возможно, вы правы. Мне нравится безопасность unique_ptr в том смысле, что вам не нужно помнить о его удалении, но, похоже, в этом случае это требует определенных затрат.   -  person jcoder    schedule 14.03.2012
comment
Настоящая безопасность unique_ptr заключается в том, что вам не нужно окружать указатель блоком try, чтобы гарантировать его удаление в случае исключения. Это означает, что объект с деструктором не принесет вам много пользы, если у вас нет особых ограничений в конструкторе. (Независимо от того, помещаете ли вы удаление в деструктор или используете unique_ptr, получается примерно одно и то же с точки зрения того, что вы должны помнить.)   -  person James Kanze    schedule 14.03.2012
comment
@ Джеймс Канце, да, в этом есть смысл. Интересный   -  person jcoder    schedule 14.03.2012
comment
@JamesKanze, если конструктор не завершается (выдает исключение), использование std::unique_ptr позволит избежать утечки памяти самым простым способом. Это не единственный способ, но он прост и рассчитан на будущее (т. Е. Более поздние модификации конструктора не будут подвержены риску утечки). В худшем случае это должно быть не хуже ручного подхода. Почему вы говорите, что в этом случае [может быть] обратное?   -  person David Rodríguez - dribeas    schedule 14.03.2012
comment
@ DavidRodríguez-dribeas Нет простого, универсального ответа. Судя по его описанию, я не вижу проблемы, которую std::unique_ptr помогло бы решить. Это просто добавило сложности и запутывания. В более сложных ситуациях это зависит. В большинстве случаев более чистым решением было бы делегирование базовому классу, который отвечает только за один объект - за этот объект. (Очень хороший пример этого - реализация std::vector в g ++.) В других случаях имеет смысл иметь член unique_ptr, но, по моему опыту, они не очень распространены.   -  person James Kanze    schedule 14.03.2012


Ответы (5)


В стиле Современный C ++ есть две ключевые концепции:

  • Право собственности
  • Недействительность

Право собственности касается владельца некоторого объекта / ресурса (в данном случае экземпляра Device). Различные std::unique_ptr, boost::scoped_ptr или std::shared_ptr относятся к собственности.

Однако Nullity намного проще: он просто выражает, может ли данный объект быть нулевым, и не заботится ни о чем другом, и уж точно не о праве собственности!


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

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


С другой стороны, большинству пользователей ресурсов наплевать на его право собственности.

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

Таким образом, выбор способа передачи параметра зависит от его возможного Nullity:

  • Никогда не обнулять? Передайте ссылку
  • Возможно нулевое значение? Передайте указатель, простой пустой указатель или класс, подобный указателю (например, с ловушкой на null)

person Matthieu M.    schedule 14.03.2012
comment
Хм, ссылки ... Я мог бы заставить свои функции принимать параметры устройства и устройства, а затем в вызывающем коде использовать * device_, поскольку это все равно будет работать с unique_ptr, и я знаю, что device_ не может быть нулевым (и могу проверить это с помощью утверждений). Теперь мне нужно немного поразмышлять над этим и решить, некрасиво это или элегантно! - person jcoder; 14.03.2012
comment
@JohnB: ну, моя двоякая точка зрения состоит в том, что устранение возможности недействительности во время компиляции намного безопаснее :) - person Matthieu M.; 14.03.2012
comment
@JohnB: Полностью согласен с Матье здесь, и я бы даже пошел дальше, почему вы хотите динамически выделять объект, время жизни которого ограничено временем жизни объекта, который содержит указатель? Почему бы не объявить его простым членом с автоматическим хранением? - person David Rodríguez - dribeas; 14.03.2012

Это действительно зависит от обстоятельств. Если функция должна владеть unique_ptr, тогда ее подпись должна принимать unique_ptr<Device> bv значение, а вызывающий должен std::move указатель. Если право собственности не является проблемой, я бы сохранил подпись необработанного указателя и передал бы указатель unique_ptr с помощью get(). В этом нет ничего ужасного, если рассматриваемая функция не переходит на владение.

person juanchopanza    schedule 14.03.2012
comment
Да, я не хочу проявлять себя, просто используйте объект, на который указывает объект в функции. - person jcoder; 14.03.2012
comment
Вы также можете передать константную ссылку на unique_ptr, если право собственности не является проблемой. Некоторые люди используют дихотомию указатель-ссылка для имитации семантики входных-выходных формальных параметров Паскаля (руководство по стилю Google C ++). - person ; 05.12.2015

Я бы использовал std::unique_ptr const&. Использование ссылки, отличной от констант, даст вызываемой функции возможность сбросить указатель.
Я думаю, что это хороший способ выразить, что вызываемая функция может использовать указатель, но ничего больше.
Так что для меня это сделает интерфейс легче читается. Я знаю, что мне не нужно возиться с переданным мне указателем.

person mkaes    schedule 14.03.2012
comment
Чтобы было ясно, добавление const там будет означать, что я не могу каким-либо образом изменить unique_ptr, но я могу вызывать неконстантные функции содержащегося объекта через unique_ptr? - person jcoder; 14.03.2012
comment
@JohnB: Да, константа предотвращает вызов вызываемой функции, например, сброс на вашем unique_ptr. Но все же, пока указатель внутри не является константным, вы можете вызывать неконстантные функции. - person mkaes; 14.03.2012
comment
Да, ты можешь. get () const метод unique_ptr возвращает неконстантный указатель на объект, охраняемый unique_ptr - person zabulus; 14.03.2012
comment
@mkaes Я бы сказал, что передача константной ссылки на уникальный указатель менее очевидна, чем передача (возможно, квалифицированного cv) необработанного указателя в этом случае. - person juanchopanza; 14.03.2012
comment
@juanchopanza: Я бы сказал, что с помощью необработанного указателя всегда сложно четко определить право собственности. Уникальный_ptr решает эту проблему хорошо. - person mkaes; 14.03.2012
comment
@mkaes Если дизайн не проясняет это, то использование unique_ptr ничего не изменит. Если вы понимаете роль и обязанности класса, тогда должно быть ясно, нужно ли вам удалить в деструкторе или нет. Если вы не знаете, что должен делать класс, unique_ptr вам не скажет. - person James Kanze; 14.03.2012
comment
Возможно, я ошибаюсь, но мне кажется, что выбор параметра типа const std::unique_ptr<T>& всегда неоптимален. С const, которое вы говорите, я не отниму у вас (вызывающего) объект (право собственности). Но с частью std::unique_ptr вы говорите, что вы должны быть (прямым) владельцем объекта, на который указывает. Но, учитывая, что я (называемая функцией) не собираюсь брать на себя право собственности, мне не нужно знать, кому принадлежит. Обратите внимание, что если вызывающий объект арендует указатель (имеет доступ, но не владеет), он не может построить из него unique_ptr без создания двойного владения. - person Marc van Leeuwen; 22.06.2014

Лучше всего, вероятно, не использовать std::unique_ptr в этом случае, хотя это зависит от обстоятельств. (Обычно у вас не должно быть более одного необработанного указателя на динамически выделяемый объект в классе. Хотя это также зависит.) Единственное, что вы не хотите делать в этом случае, - это передавать std::unique_ptr (и как вы заметил, std::unique_ptr<> const& немного громоздко и запутанно). Если это единственный динамически выделяемый указатель в объекте, я бы просто использовал необработанный указатель и delete в деструкторе. Если таких указателей несколько, я бы рассмотрел возможность отнесения каждого из них к отдельному базовому классу (где они все еще могут быть необработанными указателями).

person James Kanze    schedule 14.03.2012

Для вас это может оказаться невозможным, но замена каждого появления Device* на const unique_ptr<Device>& - хорошее начало.

Очевидно, вы не можете копировать unique_ptrs и не хотите его перемещать. Замена ссылкой на unique_ptr позволяет продолжить работу тела существующих функций.

Теперь есть ловушка, вы должны пройти мимо const &, чтобы не дать вызываемым объектам сделать unique_ptr.reset() или unique_ptr().release(). Обратите внимание, что это по-прежнему передает изменяемый указатель на устройство. С помощью этого решения у вас нет простого способа передать указатель или ссылку на const Device.

person J.N.    schedule 14.03.2012
comment
Поскольку const unique_ptr ‹Device› & является громоздким, я бы напечатал его для Device_t или аналогичного. - person Goswin von Brederlow; 22.11.2014