c++11 - Владение и геттеры

Я новичок в С++, и у меня проблемы с пониманием владения, особенно с геттером. Вот пример кода:

class GameObject {
public:
  Transform *transform();
private:
  Transform _transform;
};

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

  1. Поэтому я подумал об использовании unique_ptr для члена преобразования, поскольку GameObject — единственный, которому принадлежит преобразование. Но я не могу вернуть это от геттера, не так ли? Но опять же, зачем мне вообще использовать unique_ptr вместо того, чтобы добавлять его в качестве члена, как описано выше?

  2. Так почему бы не использовать shared_ptr? Мне это просто кажется неправильным, я не хочу делиться собственностью, GameObject является владельцем, и другие могут получить к нему доступ...

  3. Так что же это? Ссылка? Я предполагаю, что shared_ptr кажется самым мудрым выбором, так как другие могут безопасно сохранить ссылку на преобразование, но что хорошего в том, если закрывающий GameObject будет уничтожен, что сделает преобразование бесполезным? Я, вероятно, просто неправильно думаю о собственности, но мне все кажется неправильным. Спасибо за вашу помощь.


person cboe    schedule 11.06.2013    source источник
comment
Вы можете сохранить член _transform через shared_ptr и вернуть weak_ptr из вашей функции-получателя.   -  person inkooboo    schedule 12.06.2013


Ответы (5)


Для любого, кто читает ваше определение класса, очевидно (или должно быть очевидно), что GameObject владеет классом transform. Вы правы в том, что «совместное владение» не подразумевается и не желательно. Поскольку нет никакой возможности когда-либо получить transform из GameObject, вам не нужно что-то, что выражает возможную нуль, например указатель (необработанный или иной), поэтому возврат ссылки (возможно) const кажется наиболее идиоматической вещью для делать.

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

person CB Bailey    schedule 11.06.2013
comment
+1 за разработку решений, чтобы было трудно делать что-то неправильно. Это основная философия библиотеки boost. - person Vite Falcon; 12.06.2013
comment
Всем отличные ответы, большое спасибо! @Balog Pal: Тогда не будут ли задействованы копии? - person cboe; 12.06.2013
comment
@cboe: да, в этом суть. Вы избегаете утечки кишок. Если полученный элемент не является копией коллекции, в большинстве случаев он вряд ли будет заметен. - person Balog Pal; 12.06.2013
comment
@BalogPal: Да, возврат по значению, безусловно, позволяет избежать любых проблем с продолжительностью жизни; Я сделал неявное предположение, что преобразование было скорее сущностью, чем значением, но это не обязательно было правильным предположением. - person CB Bailey; 12.06.2013
comment
Определенно больше сущности, чем ценности, но я даже не думал об этом :) Спасибо - person cboe; 12.06.2013

Я думаю, что важно то, что ваш дизайн моделирует тот факт, что объект Transform принадлежит его GameObject.

Если владение не предназначено для совместного использования (другими словами, если время жизни объекта Transform строго ограничено временем жизни единственного GameObject, которому он принадлежит), то я бы избегал shared_ptr.

На мой взгляд, ваш первый выбор должен быть тем, который вы на самом деле уже выбрали: просто иметь подобъект типа Transform. Однако верните ссылку на него, а не необработанный указатель:

class GameObject {
public:
    Transform& transform();
private:
    Transform _transform;
};

Это самый четкий способ сообщить о том, что вы предоставляете доступ к подобъекту _transform, но не владеете им.

Клиентам не нужно беспокоиться о таких вопросах, как "когда и как я должен удалить этот объект? И должен ли я удалить его вообще?", потому что в вашем интерфейсе все ясно: ни права собственности, ни ответственности. .

С другой стороны, есть причины, которые могут помешать вам сделать это. Одной из возможных причин может быть то, что GameObject может владеть или не владеть объектом Transform. Если это так, вы можете вместо этого использовать unique_ptr (и возвращать необработанный указатель, который может быть нулевым).

Другая причина использования unique_ptr может заключаться в том, что создание подобъекта Transform необходимо отложить, поскольку конструктор принимает некоторые значения, которые необходимо вычислить, и которые не могут быть предоставлены в списке инициализации конструктора.

В общем, если у вас нет для этого веских причин, предпочтите самое простое решение и просто вставьте подобъект типа Transform, выдав ссылку.

person Andy Prowl    schedule 11.06.2013
comment
не могли бы вы объяснить предпоследний абзац? и если используется уникальный указатель, должен ли возвращаемый тип быть unique_ptr, который будет перемещен клиенту? - person Koushik Shetty; 12.06.2013
comment
@Koushik: если конструктор Transform должен вызываться с аргументами, требующими некоторых нетривиальных вычислений, и это вычисление нельзя выполнить в списке инициализаторов конструктора GameObject или лучше поместить в тело конструктора, тогда Transform подобъект не может быть инициализирован. Конечно, если бы у Transform был конструктор по умолчанию, то мы могли бы просто позволить подобъекту _transform быть сконструированным по умолчанию, а позже в теле мы бы переназначили его или изменили. - person Andy Prowl; 12.06.2013
comment
@Koushik: Или даже объект Transform может быть создан клиентом и передан GameObject через unique_ptr. В любом случае GameObject не будет предоставлять доступ к принадлежащему указателю _transform, возвращая unique_ptr. Возврат unique_ptr будет означать передачу права собственности, что здесь нежелательно. GameObject будет либо возвращать ссылку, либо необработанный указатель (в зависимости от того, всегда ли объект Transform внедряется GameObject или нет) - person Andy Prowl; 12.06.2013
comment
Я понимаю эту часть, но я не понимаю, как можно использовать unique_ptr для отсрочки построения преобразования. - person Koushik Shetty; 12.06.2013
comment
@ AndyProwl а теперь я понял . Спасибо:) - person Koushik Shetty; 12.06.2013

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

В вашем примере _transform очень хорош как прямой член. Если вам нужен геттер, вы можете сделать это

Transform transform() const {return _transform; }

В некоторых особых случаях это может быть

const Transform& transform() const {return _transform; }

с пожизненной документацией. Делайте это только в том случае, если это оправдано.

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

person Balog Pal    schedule 11.06.2013
comment
Когда требуется передача права собственности, требуется указатель. Это звучит более понятно, чем ваше эмпирическое правило. - person aggsol; 12.06.2013
comment
Я не вижу противоречия. Есть хорошие треды, перечисляющие случаи, когда использование указателя неизбежно, ваш случай есть в списке. - person Balog Pal; 12.06.2013

Собственность и необработанные указатели?

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

В моем собственном коде я решил следующее соглашение:

необработанные указатели означают отсутствие передачи прав собственности/совместного использования

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

Итак, в вашем случае вы можете вернуть указатель вместо умного указателя.

Указатель или ссылка?

Это означает, что выбор между указателем и ссылкой зависит только от одного фактора, определяемого следующим правилом:

  • Если значение nullptr/NULL имеет смысл, используйте указатели.
  • Если значение nullptr/NULL не имеет смысла и/или нежелательно, используйте ссылки.

константность и переменные-члены?

Я видел несколько ответов на постоянство, поэтому добавляю свои два цента:

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

const Transform * getTransform() const ;
Transform * getTransform() ;

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

Transform * getTransform() const ;

Я вызываю (оскорбительно) метод доступа над нижним индексом, так как в массивах указателей массив может быть константным, и, следовательно, внутренний адрес является константным, но данные, на которые указывает этот адрес, неконстантны, поэтому их можно изменить. Обычно это означает, что объект Transform на самом деле не принадлежит вашему объекту GameObject. Действительно, возможно, GameObject ссылается только на какой-то внешний объект для простоты использования.

person paercebal    schedule 12.06.2013

Другие уже ответили на вопрос о праве собственности, но я все же хотел бы добавить еще один момент.

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

class GameObject {
public:
  Transform _transform; // and you don't have to write a getter
};

Но это плохая идея.

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

person Ali    schedule 11.06.2013
comment
Звучит правильно, но разве это не правильный способ сделать его доступным только для чтения? - person cboe; 12.06.2013
comment
@cboe Да, абсолютно, это было бы нормально. - person Ali; 12.06.2013