Предположим, у меня есть класс, в котором я хочу, чтобы пользователь мог ссылаться на одного из моих членов. Что предпочтительнее?
class Member;
class ClassWithWeakPtr
{
private:
boost::shared_ptr<Member> _member;
public:
boost::weak_ptr<Member> GetMember();
};
или же
class Member;
class ClassWithCppReference
{
private:
Member _member;
public:
Member& GetMember() {return _member;}
};
Что вы думаете? Когда одно лучше другого?





Моя основная, невежественная рекомендация:
Я понимаю, что последнее может быть небезопасным, если экземпляр ClassWithCppReference может исчезнуть, пока у кого-то еще есть ссылка на член. Однако я также вижу аргумент для последнего класса с типами POD, например, сообщение на устройство, и вы не хотите постоянно копировать член.
Почему бы не вернуть shared_ptr<>? Таким образом, клиент может использовать то, что возвращается, столько, сколько ему нужно, но нет проблем, если класс «сервер» исчезнет.
Существует не так много ситуаций, в которых семантика weak_ptr<> имеет большой смысл (кеши и ???). Обычно, когда клиент о чем-то просит, он хочет, чтобы право собственности на эту вещь определялось самим клиентом (а совместное владение так же хорошо, как и полное владение).
Если у вас есть ситуация, когда объект «сервер» может быть уничтожен без ведома клиента (ситуация, когда вам может понадобиться use weak_ptr<> или shared_ptr<>), худшее, что вы можете сделать, - это вернуть ссылку на участника. В этом случае клиент не может знать, безопасно ли получить доступ к возвращенной ссылке. Вы должны вернуть копию члена или интеллектуальный указатель, который может правильно управлять временем жизни возвращаемого члена.
Помните, что в случае, когда есть выражение, которое создает временный ClassWithCppReference (что не всегда очевидно), клиент, вызывающий GetMember(), даже не сможет использовать возвращенную ссылку в следующем операторе.
Вам следует избегать раздачи своих внутренних компонентов; это руководство n. 42 «Стандартов кодирования C++» (Херб Саттер и Андрей Александреску). Если по каким-то причинам это необходимо, лучше вернуть ссылка на константу, а не указатель, потому что постоянство не распространяется через него.
weak_ptr <> кажется жизнеспособным решением, даже если его основная цель - избежать циклов shared_ptr. Вместо этого, если вы возвращаете shared_ptr <>, вы продлеваете жизнь такого внутреннего, что в большинстве случаев не имеет смысла.
Проблема с экземпляром, который исчезает, когда кто-то обрабатывает ссылку на его внутренние компоненты, должна быть связана с правильной синхронизацией / обменом данными между потоками.
Я согласен с вами awgn. Я ленился при публикации и не добавлял константу.
Я думаю, что единственный разумный ответ: это зависит от того, как Member относится к классу и что вы хотите, чтобы пользователи класса могли делать. Имеет ли _member значимое существование, независимое от объекта Class? Если нет, то я не думаю, что использование shared_ptr имеет смысл, независимо от того, возвращаете ли вы weak_ptr или shared_ptr. По сути, любой из них будет предоставлять пользователю доступ к объекту Member, который может пережить объект Class, который придает ему значение. Это могло бы предотвратить сбой, но за счет сокрытия серьезной ошибки проектирования.
Как указывает awgn, вы должны быть очень осторожны с открытием внутренних компонентов вашего класса. Но я думаю, для этого определенно есть место. Например, у меня есть класс в моем коде, который представляет объект файла, состоящий из объекта заголовка файла и объекта данных файла. Полное дублирование интерфейса заголовка в классе файла было бы глупо и нарушило бы DRY. Я полагаю, вы могли бы заставить пользователя получить копию объекта заголовка, внести изменения в копию, а затем скопировать внешний объект обратно в общий класс файла. Но это приводит к большой неэффективности, которая только дает вам возможность сделать представление заголовка файлового объекта отличным от представления объекта заголовка. Если вы уверены, что это не то, что вам нужно, вы можете также вернуть неконстантную ссылку на заголовок - это проще и эффективнее.
Я хочу поднять кое-что в ответ на комментарии (в основном от OP и Colomon) об эффективности и т. д. Часто копирование чего-то действительно не имеет значения с точки зрения реальной производительности.
Я написал программы, в которых много защитное копирование. Это идиома в Java, где, поскольку все объекты передаются по указателю, происходит много псевдонимов, поэтому вы копируете все, что входит / выходит из вашего класса, чтобы сломать псевдоним, гарантируя, что клиенты вашего класса не могут нарушить инварианты вашего класса путем модификации объекта постфактум.
Программы, которые я написал, в целях защиты копировали целые структуры местами, включая целые списки и карты. Чтобы убедиться, что это не повлияет на производительность, я тщательно профилировал программу. Основные узкие места были где-то в другом месте (и даже тогда я настроил эти другие места так, чтобы основным узким местом оставалась сеть). Нигде это защитное копирование фигуры не попало в горячие точки программы.
ETA: Я чувствую необходимость прояснить суть моего сообщения, поскольку один комментатор прочитал его не так, как я предполагал, и, вполне возможно, другие сделали то же самое. Я не хочу сказать, что копировать вещи - это нормально; скорее, нужно всегда профилировать производительность их кода, а не дико гадать, как он будет работать.
Иногда, когда копирование целых структур по-прежнему дает приемлемую производительность и в то же время упрощает написание кода, то, на мой взгляд, это лучший компромисс (в большинстве случаев), чем код, который ненамного быстрее, но намного сложнее.
Можете ли вы с помощью профилировщика точно определить, систематически ли вы вносите некоторую неэффективность в свой код? Если все в вашем коде выполняется вдвое медленнее, чем должно, профилировщик ничего не заметит, но ваша программа все равно будет работать медленно.
(Конечно, если вы находитесь в ситуации, когда разделение двух классов важно, настоятельно рекомендуется использовать копии вместо неконстантной ссылки!)
ресурсы, такие как ввод-вывод, не затронуты, так что да, профилировщик все равно найдет ваши горячие точки, пока все узкие места в ресурсах не исчезнут. не многие приложения привязаны к оперативной памяти.
Программа Java запускается на виртуальной машине и использует память, управляемую виртуальной машиной, которая самооптимизируется, собирает мусор и полностью отличается от того, что m делает фактические запросы на выделение памяти к ядру. Мне кажется, что экстраполяция выглядит довольно смело.
@ Эдуард: Мой ответ не предполагал никакой экстраполяции. Возвращаемое сообщение, если таковое имеется, состоит в том, что нужно мера, а не делать диких предположений. (На самом деле я не говорил, что копирование вещей волей-неволей - это нормально; это не так, даже в управляемых средах.)
В зависимости от контекста любой из них может подойти. Основная проблема с возвратом «живой» ссылки на член (если вам нужно предоставить ее в первую очередь) заключается в том, что кто бы ни использовал этот член, ваш клиент может удерживать его дольше, чем существует содержащий объект. И если ваш клиент обращается к указанному члену через ссылку, когда содержащийся в нем объект выходит за пределы области видимости, вы столкнетесь с «странными» сбоями, неправильными значениями и подобными забавами. Не рекомендуется.
Возврат weak_ptr <> имеет главное преимущество, так как он может сообщить клиенту, что объект, к которому они пытаются получить доступ, исчез, что ссылка не может сделать.
Стоимость моих 2 пенни будет:
Эээ ... разве вы не можете повысить weak_ptr до shared_ptr? Итак, если вы доберетесь до него до того, как исчезнет исходный shared_ptr, вы можете искусственно продлить время жизни члена другого объекта, даже если этот член был возвращен с weak_ptr.
Да, вы можете (вам действительно нужно, чтобы использовать объект), и да, вы правы, что можете использовать его, чтобы продлить время жизни объекта. Это только немного усложняет случайный выстрел вам в ногу, но не защищает вас от умысла ...
Таким образом, с weak_ptr область referand "только" неправильно расширена, в то время как пользователь повысил weak_ptr до shared_ptr. Если увеличивать продолжительность объекта неверно, то немного увеличивать его, вероятно, тоже неправильно, и теперь у вас есть ошибка с меньшим размером. Не лучше ...
Возврат слабого указателя определенно будет дороже и бесполезен - в любом случае вы не можете удерживать право собственности дольше, чем срок службы основного объекта.
Зачем возвращать слабый указатель? Я думаю, вы усложняете это без необходимой пользы.
В большинстве случаев я бы предоставил
const Member& GetMember() const;
Member& GetMember(); // ideally, only if clients can modify it without
// breaking any invariants of the Class
weak_ptr< Member > GetMemberWptr(); // only if there is a specific need
// for holding a weak pointer.
Обоснование этого состоит в том, что (также я и, вероятно, многие другие) вызов метода GetMember() подразумевает, что Class владеет member и, следовательно, возвращенная ссылка
будет действителен только в течение всего времени существования содержащего объекта.
В большинстве (но не во всем) коде, с которым я сталкивался, методы GetMember() обычно используются для немедленной обработки возвращенного члена, а не для его сохранения для последующего пользователя. В конце концов, если вам нужен доступ к члену, вы всегда можете запросить его у содержащего объекта в любое время, когда это потребуется.
Синхронизация не обязательно решает проблему с возвратом ссылки. Если GetMember () вызывается временно, ссылка почти сразу становится недействительной.