Когда использовать shared_ptr и когда использовать необработанные указатели?

class B;

class A
{
public:
    A ()
        : m_b(new B())
    {
    }

    shared_ptr<B> GimmeB ()
    {
        return m_b;
    }

private:
    shared_ptr<B> m_b;
};

Пусть говорят, что B - это класс, который семантически не должен существовать вне времени жизни A, т.е. абсолютно не имеет смысла, чтобы B существовал сам по себе. Если GimmeB возвращает a shared_ptr<B> или B*?

В целом, хорошая практика - полностью избегать использования необработанных указателей в коде на С++ вместо интеллектуальных указателей?

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

Ответ 1

Ваш анализ вполне правильный, я думаю. В этой ситуации я также вернул бы голый B* или даже [const] B&, если объект никогда не будет равен нулю.

Имея некоторое время для изучения интеллектуальных указателей, я пришел к некоторым рекомендациям, которые рассказывают мне, что делать во многих случаях:

  • Если вы возвращаете объект, срок службы которого должен управляться вызывающим абонентом, верните std::unique_ptr. Вызывающий может назначить его std::shared_ptr, если он хочет.
  • Возвращение std::shared_ptr на самом деле довольно редко, и, когда это имеет смысл, это обычно очевидно: вы указываете вызывающему, что он продлит время жизни объекта, указанного за объектом, за время жизни объекта, который первоначально поддерживал ресурс. Возвращение общих указателей с фабрик не является исключением: вы должны это сделать, например. когда вы используете std::enable_shared_from_this.
  • Вам очень редко требуется std::weak_ptr, за исключением случаев, когда вы хотите понять метод lock. У этого есть некоторые виды использования, но они редки. В вашем примере, если время жизни объекта A не было детерминированным с точки зрения вызывающего, это было бы чем-то, что нужно учитывать.
  • Если вы вернете ссылку на существующий объект, время жизни которого вызывающий не может контролировать, верните пустой указатель или ссылку. Поступая таким образом, вы говорите вызывающему, что объект существует и что ей не нужно заботиться о его жизни. Вы должны вернуть ссылку, если вы не используете значение nullptr.

Ответ 2

Вопрос "когда следует использовать shared_ptr и когда следует использовать необработанные указатели?" имеет очень простой ответ:

  • Используйте необработанные указатели, если вы не хотите, чтобы какое-либо право собственности было привязано к указателю. Эта работа также часто может выполняться со ссылками. Необработанные указатели также могут использоваться в некоторых низкоуровневых кодах (например, для реализации интеллектуальных указателей или реализации контейнеров).
  • Используйте unique_ptr или scope_ptr, если вам нужна уникальная собственность на объект. Это самый полезный вариант, и его следует использовать в большинстве случаев. Уникальное право собственности также можно выразить простым созданием объекта напрямую, вместо использования указателя (это даже лучше, чем использование unique_ptr, если это можно сделать).
  • Используйте shared_ptr или intrusive_ptr, если вам требуется совместное владение указателем. Это может быть запутанным и неэффективным, и часто это не очень хороший вариант. Совместное владение может быть полезно в некоторых сложных проектах, но его следует избегать в целом, потому что это приводит к тому, что код трудно понять.

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

Ответ 3

Вот хорошее эмпирическое правило:

  • Когда нет передачи или совместного владения, ссылки или простые указатели достаточно хороши. (Простые указатели более гибкие, чем ссылки.)
  • Когда есть передача права собственности, но нет совместного владения, тогда std::unique_ptr<> является хорошим выбором. Часто бывает с заводскими функциями.
  • Когда есть совместное владение, тогда это хороший вариант использования для std::shared_ptr<> или boost::intrusive_ptr<>.

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

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

Ответ 4

Если вы не хотите, чтобы вызывающий GimmeB() мог продлить время жизни указателя, сохранив копию ptr после того, как экземпляр A умирает, вы определенно не должны возвращать shared_ptr.

Если вызываемый вызов не должен удерживать возвращаемый указатель в течение длительных периодов времени, то есть нет риска того, что экземпляр времени жизни A истекает до указателя, тогда исходный указатель будет лучше. Но даже лучший выбор - просто использовать ссылку, если нет веских оснований для использования фактического необработанного указателя.

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

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

Ответ 5

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

В вашем конкретном случае: почему бы не вернуть ссылку?

Указатель предполагает, что данные могут быть нулевыми, однако в вашем A всегда будет B, поэтому он никогда не будет равен нулю. Ссылка ссылается на это поведение.

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

Примечание: есть тонкая ошибка с shared_ptr, копия A по умолчанию будет использовать те же B, что и оригинал, если вы явно не пишете конструктор копирования и оператор присваивания копии. И, конечно, вы бы не использовали необработанный указатель в A для хранения B, не могли бы вы:)?


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

Вы не должны возвращать ручки своим внутренним частям (см. Закон Деметры).

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

Ответ 6

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

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

Ответ 7

  • Вы выделяете B при построении A.
  • Вы говорите, что B не должен оставаться снаружи. Как жизнь.
    Оба они указывают на то, что B является членом A и просто возвращает ссылочный аксессуар. Вы переоцениваете это?

Ответ 8

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

Ответ 9

Я обнаружил, что в Основных рекомендациях C++ есть несколько очень полезных советов по этому вопросу:

Использование необработанного указателя (T *) или более умного указателя зависит от того, кому принадлежит объект (чья ответственность освобождать память объекта).

своя:

smart pointer, owner<T*>

не владеть:

T*, T&, span<>

владелец <>, span <> определен в библиотеке Microsoft GSL

вот правила большого пальца:

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

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

3) Т * или владелец обозначают отдельный объект (только)

4) использовать вектор/массив/диапазон для массива

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

Ответ 10

Когда вы говорите: "Пусть говорят, что B - это класс, который семантически не должен существовать вне времени жизни A"

Это говорит мне, что B должен логически не существовать без A, но как насчет физически существующего? Если вы можете быть уверены, что никто не будет пытаться использовать * B после A dtors, чем, возможно, необработанный указатель будет в порядке. В противном случае может оказаться целесообразным указатель.

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