Должен ли я использовать shared_ptr или unique_ptr

Я создавал некоторые объекты с помощью идиомы pimpl, но не уверен, следует ли использовать std::shared_ptr или std::unique_ptr.

Я понимаю, что std::unique_ptr более эффективен, но для меня это не такая большая проблема, поскольку эти объекты в любом случае относительно тяжелые, поэтому стоимость std::shared_ptr по сравнению с std::unique_ptr относительно невелика.

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

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

Это правильно?


person Clinton    schedule 07.04.2011    source источник
comment
Использование того или другого глубоко меняет семантику копирования, которую вы придаете своим объектам. Есть применение для обоих. Я бы сказал, что более идиоматичным в мире C++ является unique_ptr, но объекты с общими реализациями имеют свое применение, особенно если вы пишете иностранный код (например, COM, C++/CLI) или если класс действительно выглядит как ссылка тип.   -  person Alexandre C.    schedule 07.04.2011
comment
Аналогичный вопрос: stackoverflow .com/questions/311166/   -  person Rolf Kristensen    schedule 22.11.2011
comment
Рекомендуемый способ в C++11 — использовать unique_ptr, ведь вам не нужно копировать или делиться реализацией с кем-либо. Кроме того, unique_ptr работает быстрее во время выполнения.   -  person Damian    schedule 08.06.2016


Ответы (4)


Я делал некоторые объекты, используя идиому pimpl, но я не уверен, использовать ли shared_ptr или unique_ptr.

Определенно unique_ptr или scoped_ptr.

Pimpl — это не шаблон, а идиома, имеющая дело с зависимостью во время компиляции и бинарной совместимостью. Это не должно влиять на семантику объектов, особенно в отношении их поведения при копировании.

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

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

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


Не имеет отношения к прыщам

Я понимаю, что unique_ptr более эффективен, но для меня это не такая большая проблема, так как эти объекты в любом случае относительно тяжелые, поэтому стоимость shared_ptr по сравнению с unique_ptr относительно невелика.

Если вы можете выделить некоторое состояние, вы можете взглянуть на шаблон Flyweight.

person Matthieu M.    schedule 07.04.2011
comment
Итак, вы говорите, что обычно конструкторы копирования должны делать глубокие копии (или что-то функционально эквивалентное, то есть копирование при записи), за исключением указателей/умных указателей? - person Clinton; 07.04.2011
comment
@Clinton: семантически копия отделена от источника, детали реализации не представляют интереса для пользователя :) В C++ это не относится к указателям и ссылкам, но словарный запас никогда не адаптировался, поэтому неловкость, когда обсуждение этих аспектов. .. Независимо от того, предпочитаете ли вы в вашем случае глубокую копию или поверхностную копию, это должно зависеть только от семантики, которую вы хотите дать своему классу, и реализация будет следовать набору. - person Matthieu M.; 07.04.2011
comment
однако словарный запас так и не был адаптирован Что вы имеете в виду? - person curiousguy; 08.10.2011
comment
@curiousguy: 5 месяцев спустя :x ? Я думаю, что моя точка зрения заключалась в том, что конструктор копирования, делающий поверхностную копию, был довольно странным, но на самом деле... Я не уверен: p - person Matthieu M.; 08.10.2011
comment
В некоторых исключительных случаях вы действительно хотите использовать shared_ptr. Вам может понадобиться объект Foo, реализация которого может совместно использоваться многими объектами Foo с использованием подсчета ссылок и семантики копирования при записи. - person Frank; 01.06.2012
comment
@Frank: COW - это вариант (и я сказал, что shared_ptr был адаптирован для некоторых случаев), но здесь мы говорили о Pimpl; это не один из тех случаев, когда shared_ptr адаптирован. - person Matthieu M.; 01.06.2012
comment
В чем разница между шаблоном и идиомой? - person conio; 13.06.2017
comment
@conio: определение, которое я применяю, состоит в том, что шаблон — это что-то, что полезно в большом количестве языков (например, знаменитые шаблоны проектирования из «Банды четырех» применимы к любому языку, в котором вы можете использовать объектно-ориентированный дизайн), тогда как идиома специфична для определенного языка (или нескольких). PIMPL — это, в частности, идиома C или C++, поскольку она была создана для обрезки файлов заголовков. - person Matthieu M.; 13.06.2017
comment
@MatthieuM. Что ж, вы имеете право на свое мнение, но по сути вы утверждаете, что CRTP не существует. Может быть, есть CRTI. Я не уверен, что это общепринятое или разумное мнение или определение. - person conio; 13.06.2017
comment
@conio: CRTP не специфичен для C или C ++, я ожидаю, что его можно использовать, по крайней мере, в D и, возможно, в Java / C # (не уверен, поскольку, похоже, требуются неограниченные дженерики). В любом случае, термины, безусловно, пересекаются, и у других людей разные мнения об их значении, ... это немного неразбериха :x - person Matthieu M.; 13.06.2017
comment
@MatthieuM. То же самое касается прыща. Это тоже не относится к C++. Его также можно использовать в D. dlang.org/spec/struct.html stackoverflow.com/questions/20469768/ - person conio; 13.06.2017
comment
@conio: Сама страница, на которую вы ссылаетесь, называет ее идиомой PIMPL;) В любом случае, только потому, что ее можно использовать в D поверх C или C++, не обязательно нарушает мое определение; это все еще ниша. - person Matthieu M.; 13.06.2017
comment
@MatthieuM. Но эта веб-страница не утверждает, что идиома и шаблон исключают друг друга, как вы это сделали. PIMPL можно использовать в D поверх C так же, как CRTP доступен в D поверх C и в C#/Java поверх синтаксиса дженериков, скопированного непосредственно из C++. Когда вы меняете свое определение, это признак того, что вы ошиблись. Вы сначала решили, что идиома и паттерн взаимоисключающие, и не пытаетесь придумать определение, которое заставит это работать. Отлично. Я понял. - person conio; 14.06.2017

Если вы используете shared_ptr, это не совсем классическая идиома pimpl (если вы не предпримете дополнительные шаги). Но настоящий вопрос заключается в том, почему вы хотите использовать интеллектуальный указатель для начала; совершенно ясно, где должно произойти delete, и нет проблем с безопасностью исключений или чем-то другим, о чем нужно беспокоиться. В лучшем случае умный указатель сэкономит вам одну-две строки кода. И единственная, которая имеет правильную семантику, это boost::scoped_ptr, и я не думаю, что она работает в данном случае. (IIRC, для создания экземпляра требуется полный тип, но я могу ошибаться.)

Важным аспектом идиомы pimpl является то, что ее использование должно быть прозрачным для клиента; класс должен вести себя точно так же, как если бы он был реализован классически. Это означает либо запрещение копирования и присваивания, либо реализацию глубокого копирования, если только класс не является неизменяемым (нет неконстантных функций-членов). Ни один из обычных интеллектуальных указателей не реализует глубокое копирование; вы, конечно, можете реализовать его, но, вероятно, все равно потребуется полный тип всякий раз, когда происходит копирование, а это означает, что вам все равно придется предоставить определяемый пользователем конструктор копирования и оператор присваивания (поскольку они не могут быть встроенными). Учитывая это, вероятно, не стоит заморачиваться с использованием интеллектуального указателя.

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

person James Kanze    schedule 07.04.2011

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

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

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

person Nim    schedule 07.04.2011
comment
Это хороший совет, но я думаю, что всегда передавать и возвращать по значению немного сложно. Предпочтительно передавать по ссылке const или ссылке rvalue. Прохождение простой, изменяемой ссылки даже иногда уместно. - person Potatoswatter; 07.04.2011
comment
@Johanne: А, я думал о влиянии передачи по значению на объект с членом (pimpl) shared_ptr или unique_ptr. В этом контексте сам интеллектуальный указатель не должен передаваться. - person Potatoswatter; 07.04.2011
comment
@Johann Gerell: Нет, вы не должны передавать shared_ptr по значению. Вы можете, но это не рекомендуемый способ. Рекомендуемый способ - передать их по константной ссылке. Это более эффективно, чем копирование shared_ptr, а также абсолютно безопасно, поскольку параллельное чтение одного и того же экземпляра shared_ptr допустимо в соответствии с документацией по shared_ptr. - person antred; 26.08.2014

Да, пожалуйста, используйте их. Проще говоря, shared_ptr — это реализация интеллектуального указателя. unique_ptr — это реализация автоматического указателя:

person Trombe    schedule 12.06.2017