Подтвердить что ты не робот

С++ 11 - Собственность и получатели

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

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

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

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

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

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

4b9b3361

Ответ 1

Очевидно (или должно быть очевидно) любому, кто читает ваше определение класса, < <20 > принадлежит transform. Вы правы, что "совместное владение" не подразумевается или не требуется. Поскольку нет возможности когда-либо получить transform от a GameObject, вам не нужно что-то, что выражает возможную нулевость, как указатель (raw или иначе), поэтому возвращает ссылку (возможно) const кажется самой идиоматической задачей.

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

Ответ 2

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

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

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

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

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

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

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

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

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

Ответ 3

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

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

Transform transform() const {return _transform; }

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

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

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

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

Ответ 4

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

const Transform * getTransform() const ;
void setTransform(Transform * p_transform) ;

Я вызываю (оскорбительно) аксессоров выше "getter/setter": если пользователь хочет изменить значение преобразования, он должен использовать setTransform. У вас намного больше контроля над тем, что вы делаете (обратите внимание, что в моем коде установщик вы передаете unique_ptr или auto_ptr, чтобы выразить приобретение полной собственности на объект p_transform. Вы заметите, что если GameObject является const, нет способа изменить свой внутренний объект Transform (сеттер не const const).

Transform * getTransform() const ;

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

Ответ 5

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

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

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

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

Если вам действительно нужно написать getter, дайте ссылку на const для частного участника, предложенную Balog Pal. Если вам нужна неконстантная ссылка, вы должны пересмотреть свой дизайн.