Я новичок в C ++, и у меня есть проблемы, связанные с владением, особенно с геттером. Вот пример кода:
class GameObject {
public:
Transform *transform();
private:
Transform _transform;
};
Я предполагаю, что необработанный указатель небезопасен для использования, так как кто-то может получить к нему доступ позже, когда объект больше не существует?
Поэтому я подумал об использовании unique_ptr для члена преобразования, поскольку GameObject является единственным, кто владеет преобразованием. Но я не могу вернуть это от добытчика, не так ли? Но опять же, зачем мне вообще использовать unique_ptr вместо того, чтобы добавлять его в качестве члена, как описано выше?
Так почему бы не использовать shared_ptr? Мне кажется, что это неправильно, я не хочу делиться собственностью, владелец GameObject, и другие могут получить к нему доступ …
Так что же это? Ссылка? Я думаю, что shared_ptr кажется самым мудрым выбором, так как другие могут безопасно хранить ссылку на преобразование, но что хорошего в том, что, если вмещающий GameObject будет разрушен, сделав преобразование бесполезным? Я, наверное, просто думаю о том, что право собственности здесь не так, но мне все кажется неправильным. Спасибо за вашу помощь.
Для любого, кто читает определение вашего класса, очевидно (или должно быть очевидно), что GameObject
владеет transform
, Вы правы в том, что «совместное владение» не подразумевается и не является желательным. Поскольку нет никакой возможности никогда не получить transform
из GameObject
вам не нужно что-то, что выражает возможную недействительность, например указатель (необработанный или другой), поэтому возвращает ссылку (возможно) const
кажется самой идиоматичной вещью, которую нужно сделать.
Вы говорите, что необработанный указатель небезопасен, но он не более небезопасен, чем любой другой метод прямого доступа. Любой способ, которым вы предоставляете доступ к собственному объекту преобразования (а не к его копии), дает клиенту возможность взять его адрес и сохранить его по истечении срока действия владельца GameObject
, Это действительно зависит от клиента, чтобы не делать глупостей. Нет абсолютно никакого способа предотвратить это, поэтому вы должны сделать свой интерфейс простым и понятным, а его непреднамеренно использовать неправильно.
Я думаю, что важно то, что ваш дизайн моделирует тот факт, что Transform
объект принадлежит его GameObject
,
Если право собственности не предназначено для совместного использования (другими словами, если срок действия Transform
объект строго ограничен временем жизни одного-единственного GameObject
который владеет ею), то я бы избежал shared_ptr
,
На мой взгляд, ваш первый выбор должен быть тем, который вы уже выбрали: просто выберите подобъект типа Transform
, Однако верните ссылку на него, а не необработанный указатель:
class GameObject {
public:
Transform& transform();
private:
Transform _transform;
};
Это самый простой способ сообщить о том, что вы предоставляете доступ, но не давая право собственности, _transform
субобъект.
Клиенты не должны беспокоиться о таких вопросах, как «когда и как мне удалить этот объект? И я должен удалить это вообще?«, потому что ваш интерфейс ясно об этом: нет собственности, нет ответственности.
С другой стороны, существуют причины, которые могут помешать вам сделать это. Одной из возможных причин может быть то, что GameObject
может или не может владеть Transform
объект. Если это так, вы можете использовать unique_ptr
вместо этого (и вернуть необработанный указатель, который может быть нулевым).
Еще одна причина для использования unique_ptr
может быть, что строительство Transform
подобъект должен быть отложен, потому что конструктор принимает некоторые значения, которые должны быть вычислены и не могут быть предоставлены в списке инициализации конструктора.
В общем, если у вас нет веских причин для этого, предпочтите самое простое решение и просто вставьте подобъект типа Transform
, отдавая ссылку.
Правило большого пальца: вы беспокоитесь только об указателях и праве собственности, когда это абсолютно неизбежно. C ++ имеет прекрасную семантику значений (расширенную с помощью const ref), так что вы можете продвинуться довольно далеко, прежде чем захотеть использовать указатели, умные или тупые.
В вашем примере _transform очень хорошо, как прямой член.
Если вы хотите получить, вы можете сделать это
Transform transform() const {return _transform; }
В некоторых особых случаях это может быть
const Transform& transform() const {return _transform; }
с документацией жизни. Делайте это только если это оправдано.
Ну и, очевидно, лучший подход к добытчикам — это вообще их не иметь. Ориентация на объект связана с поведением, заставьте ваш класс выполнять свою работу, а не использовать его как дамп данных, который вы продолжаете высовывать и запрашивать извне.
Умные указатели обычно идут с семантикой владения, поэтому использование любого из них, когда вы не хотите передавать / делиться владением, является плохой идеей.
В своем собственном коде я решил следующее соглашение:
необработанные указатели означают отсутствие передачи права собственности
Это означает, что если некоторые функции получают необработанный указатель в качестве параметра, то он не имеет владельца (и, следовательно, не должен пытаться удалить его). Точно так же функция, возвращающая необработанный указатель, не передает / не передает владение (таким образом, опять же, вызывающая сторона не должна пытаться удалить его).
Так что в вашем случае вы можете вернуть указатель вместо умного указателя.
Это означает, что выбор между указателем и ссылкой зависит только от одного фактора, разрешенного по следующему правилу:
nullptr
/NULL
значение имеет смысл, тогда используйте указатели.nullptr
/NULL
Значение не имеет смысла и / или нежелательно, тогда используйте ссылки.Я видел несколько ответов о константности, поэтому я добавляю свои два цента:
Возврат неконстантного значения во внутреннюю переменную является выбором дизайна. Что вы должны сделать, это выбрать между следующими получателями:
const Transform * getTransform() const ;
Transform * getTransform() ;
Я называю (оскорбительно) методы доступа над «свойством», и неконстантная версия позволяет пользователю модифицировать внутренний объект Transform на досуге, без необходимости сначала спрашивать ваш класс. Вы заметите, что если GameObject является const, нет способа изменить его внутренний объект Transform (единственный доступный метод получения — это const, возвращающий const). Вы также заметите, что вы не можете изменить адрес объекта Transform внутри GameObject. Вы можете изменить указатель данных только по этому адресу (который отличается от общедоступной переменной-члена)
const Transform *& getTransform() const ;
Transform *& getTransform() ;
Это похоже на ситуацию выше, но благодаря этой ссылке пользователь имеет прямой доступ к адресу указателя, что означает, что неконстантный метод доступа более или менее похож на то, чтобы сделать переменную-член общедоступной.
const Transform * getTransform() const ;
void setTransform(Transform * p_transform) ;
Я называю (оскорбительно) методы доступа над «получателем / установщиком»: если пользователь хочет изменить значение преобразования, он должен использовать setTransform. У вас гораздо больше контроля над тем, что вы делаете (обратите внимание, что в моем коде установщик вы передаете unique_ptr или auto_ptr, чтобы выразить получение полного владения объектом p_transform. Вы заметите, что если GameObject является const , нет способа изменить его внутренний объект Transform (установщик неконстантен).
Transform * getTransform() const ;
Я называю (оскорбительно) метод доступа над «индексом», так как в массивах указателей массив может быть константным, и, таким образом, внутренний адрес является константным, но данные, указанные этим адресом, неконстантны, поэтому его можно изменить , Обычно это означает, что объект Transform на самом деле не «принадлежит» вашему объекту GameObject. Действительно, вероятно, GameObject ссылается только на некоторый внешний объект для простоты использования.
Другие уже ответили на вопрос о владении, но я все же хотел бы добавить еще один момент.
Раздавать неconst
указатель или ссылка на private
член обычно очень плохая идея: это в основном эквивалентно созданию этого частного члена public
, Другими словами, если вы пишете метод получения, возвращающий неконстантную ссылку или указатель на неконстантный член, тогда ваш код в основном эквивалентен следующему:
class GameObject {
public:
Transform _transform; // and you don't have to write a getter
};
Но это плохая идея.
Если вам действительно нужно написать геттер, дайте постоянную ссылку на частного члена, как это предложено Балогом Палом. Если вам нужна неконстантная ссылка, вам следует пересмотреть свой дизайн.