Лучше вернуть ссылку на C++ или weak_ptr?

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

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;}
};

Что вы думаете? Когда одно лучше другого?

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
13
0
8 316
9

Ответы 9

Моя основная, невежественная рекомендация:

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

Почему бы не вернуть shared_ptr<>? Таким образом, клиент может использовать то, что возвращается, столько, сколько ему нужно, но нет проблем, если класс «сервер» исчезнет.

Существует не так много ситуаций, в которых семантика weak_ptr<> имеет большой смысл (кеши и ???). Обычно, когда клиент о чем-то просит, он хочет, чтобы право собственности на эту вещь определялось самим клиентом (а совместное владение так же хорошо, как и полное владение).

Если у вас есть ситуация, когда объект «сервер» может быть уничтожен без ведома клиента (ситуация, когда вам может понадобиться use weak_ptr<> или shared_ptr<>), худшее, что вы можете сделать, - это вернуть ссылку на участника. В этом случае клиент не может знать, безопасно ли получить доступ к возвращенной ссылке. Вы должны вернуть копию члена или интеллектуальный указатель, который может правильно управлять временем жизни возвращаемого члена.

Помните, что в случае, когда есть выражение, которое создает временный ClassWithCppReference (что не всегда очевидно), клиент, вызывающий GetMember(), даже не сможет использовать возвращенную ссылку в следующем операторе.

Вам следует избегать раздачи своих внутренних компонентов; это руководство n. 42 «Стандартов кодирования C++» (Херб Саттер и Андрей Александреску). Если по каким-то причинам это необходимо, лучше вернуть ссылка на константу, а не указатель, потому что постоянство не распространяется через него.

weak_ptr <> кажется жизнеспособным решением, даже если его основная цель - избежать циклов shared_ptr. Вместо этого, если вы возвращаете shared_ptr <>, вы продлеваете жизнь такого внутреннего, что в большинстве случаев не имеет смысла.

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

Синхронизация не обязательно решает проблему с возвратом ссылки. Если GetMember () вызывается временно, ссылка почти сразу становится недействительной.

Michael Burr 18.10.2008 23:17

Я согласен с вами awgn. Я ленился при публикации и не добавлял константу.

Doug T. 18.10.2008 23:43

Я думаю, что единственный разумный ответ: это зависит от того, как Member относится к классу и что вы хотите, чтобы пользователи класса могли делать. Имеет ли _member значимое существование, независимое от объекта Class? Если нет, то я не думаю, что использование shared_ptr имеет смысл, независимо от того, возвращаете ли вы weak_ptr или shared_ptr. По сути, любой из них будет предоставлять пользователю доступ к объекту Member, который может пережить объект Class, который придает ему значение. Это могло бы предотвратить сбой, но за счет сокрытия серьезной ошибки проектирования.

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

Я хочу поднять кое-что в ответ на комментарии (в основном от OP и Colomon) об эффективности и т. д. Часто копирование чего-то действительно не имеет значения с точки зрения реальной производительности.

Я написал программы, в которых много защитное копирование. Это идиома в Java, где, поскольку все объекты передаются по указателю, происходит много псевдонимов, поэтому вы копируете все, что входит / выходит из вашего класса, чтобы сломать псевдоним, гарантируя, что клиенты вашего класса не могут нарушить инварианты вашего класса путем модификации объекта постфактум.

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


ETA: Я чувствую необходимость прояснить суть моего сообщения, поскольку один комментатор прочитал его не так, как я предполагал, и, вполне возможно, другие сделали то же самое. Я не хочу сказать, что копировать вещи - это нормально; скорее, нужно всегда профилировать производительность их кода, а не дико гадать, как он будет работать.

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

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

Sol 19.10.2008 22:10

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

Sol 19.10.2008 23:26

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

Dustin Getz 07.11.2008 02:14

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

Edouard A. 22.07.2009 16:03

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

Chris Jester-Young 22.07.2009 21:58

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

Возврат weak_ptr <> имеет главное преимущество, так как он может сообщить клиенту, что объект, к которому они пытаются получить доступ, исчез, что ссылка не может сделать.

Стоимость моих 2 пенни будет:

  • Если ни один из ваших клиентов никогда не будет использовать член, вы, как автор, единственный, кто может использовать его и контролировать время жизни объекта, возврат ссылки - это нормально. Ссылка на константу была бы еще лучше, но это не всегда возможно в реальном мире с существующим кодом.
  • Если кто-то еще будет обращаться к члену и использовать его, особенно если вы пишете библиотеку, верните weak_ptr <>. Это избавит вас от лишних хлопот и упростит отладку.
  • Я бы не стал возвращать shared_ptr <>. Я видел этот подход, и он обычно одобряется людьми, которым не нравится концепция weak_ptr / weak. Главный недостаток, который я вижу в этом, заключается в том, что он искусственно продлевает время жизни члена другого объекта за пределы его содержащего объекта. Это концептуально неверно в 99% случаев и того хуже, может превратить вашу программную ошибку (доступ к чему-то, чего больше нет) в концептуальную ошибку (доступ к чему-то, что не должен уже существует).

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

Sol 19.10.2008 21:58

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

Timo Geusch 20.10.2008 21:01

Таким образом, с weak_ptr область referand "только" неправильно расширена, в то время как пользователь повысил weak_ptr до shared_ptr. Если увеличивать продолжительность объекта неверно, то немного увеличивать его, вероятно, тоже неправильно, и теперь у вас есть ошибка с меньшим размером. Не лучше ...

Steve Jessop 21.10.2008 03:12

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

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

В большинстве случаев я бы предоставил

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() обычно используются для немедленной обработки возвращенного члена, а не для его сохранения для последующего пользователя. В конце концов, если вам нужен доступ к члену, вы всегда можете запросить его у содержащего объекта в любое время, когда это потребуется.

Другие вопросы по теме