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

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

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

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

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

Правильно ли это?

Ответ 1

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

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

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

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

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

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


Не относится к Pimpl

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

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

Ответ 2

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

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

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

Ответ 3

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

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

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

Ответ 4

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