Должен ли я предпочесть указатели или ссылки в данных участника? - PullRequest
121 голосов
/ 21 мая 2009

Это упрощенный пример для иллюстрации вопроса:

class A {};

class B
{
    B(A& a) : a(a) {}
    A& a;
};

class C
{
    C() : b(a) {} 
    A a;
    B b; 
};

Таким образом, B отвечает за обновление части C. Я пропустил код через lint, и он отозвался о ссылочном члене: lint # 1725 . Это говорит о том, что нужно бережно относиться к копированию и назначению по умолчанию, что достаточно справедливо, но копирование и назначение по умолчанию также плохо работает с указателями, поэтому у них мало преимуществ.

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

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

Правда ли, что объект, содержащий ссылку, нельзя назначать, поскольку ссылка не должна изменяться после инициализации?

Ответы [ 10 ]

131 голосов
/ 21 мая 2009

Мое эмпирическое правило:

  • Используйте ссылочный член, когда вы хотите, чтобы жизнь вашего объекта зависела от жизни других объектов : это явный способ сказать, что вы не позволяете объекту жить без допустимый экземпляр другого класса - из-за отсутствия присваивания и обязанности инициализации ссылок через конструктор. Это хороший способ спроектировать свой класс, не предполагая, что его экземпляр является членом или нет другого класса. Вы только предполагаете, что их жизни напрямую связаны с другими экземплярами. Позже вы сможете изменить способ использования экземпляра вашего класса (с новым, как локальный экземпляр, как член класса, сгенерированный пулом памяти в менеджере и т. Д.)
  • Использовать указатель в других случаях : если вы хотите, чтобы элемент был изменен позже, используйте указатель или постоянный указатель, чтобы быть уверенным, что только прочитанный указанный экземпляр. Если этот тип предполагается копируемым, вы все равно не можете использовать ссылки. Иногда вам также нужно инициализировать член после вызова специальной функции (например, init ()), и тогда у вас просто не остается выбора, кроме как использовать указатель НО: используйте asserts во всех ваших функциях-членах, чтобы быстро обнаружить неправильное состояние указателя!
  • В тех случаях, когда вы хотите, чтобы время жизни объекта зависело от времени жизни внешнего объекта, и вам также нужно, чтобы этот тип был копируемым, используйте члены-указатели, но ссылочный аргумент в конструкторе Таким образом, вы указываете на конструкции, что время жизни этого объекта зависит от времени жизни аргумента, НО реализация использует указатели, чтобы все еще быть копируемым. Пока эти члены изменяются только копией, а ваш тип не имеет конструктора по умолчанию, этот тип должен соответствовать обоим целям.
48 голосов
/ 21 мая 2009

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

Пример задач:

  • вы вынуждены инициализировать ссылку в списке инициализаторов каждого конструктора: нет способа выделить эту инициализацию в другую функцию ( до C ++ 0x, в любом случае edit: C ++ теперь имеет делегирующих конструкторов )
  • ссылка не может быть восстановлена ​​или иметь нулевое значение. Это может быть преимуществом, но если когда-либо потребуется изменить код, чтобы разрешить повторное связывание или чтобы элемент был нулевым, все варианты использования элемента должны измениться
  • в отличие от членов-указателей, ссылки не могут быть легко заменены умными указателями или итераторами, поскольку для рефакторинга может потребоваться
  • Всякий раз, когда используется ссылка, она выглядит как тип значения (оператор . и т. Д.), Но ведет себя как указатель (может свисать) - например, Руководство по стилю Google обескураживает
32 голосов
/ 21 мая 2009

Объекты редко должны позволять назначать и другие вещи, такие как сравнение. Если вы рассматриваете некоторую бизнес-модель с такими объектами, как «Отдел», «Сотрудник», «Директор», трудно представить себе случай, когда один сотрудник будет назначен другому.

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

И, вероятно, можно описать отношение один или ноль как указатель.

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

Наличие указателя в качестве члена заставит вас или члена вашей команды снова и снова проверять указатель перед использованием с комментарием «на всякий случай». Если указатель может быть нулевым, то указатель, вероятно, используется в качестве типа флага, что плохо, поскольку каждый объект должен играть свою собственную роль.

11 голосов
6 голосов
/ 21 мая 2009

Поскольку все, кажется, раздают общие правила, я предлагаю два:

  • Никогда, никогда не используйте ссылки в качестве членов класса. Я никогда не делал этого в своем собственном коде (кроме как доказать себе, что я был прав в этом правиле) и не могу представить себе случай, когда я бы это сделал. Семантика слишком запутанная, и это действительно не то, для чего были разработаны ссылки.

  • Всегда, всегда, используйте ссылки при передаче параметров в функции, за исключением базовых типов или когда алгоритму требуется копия.

Эти правила просты и помогли мне. Я оставляю правила использования умных указателей (но не auto_ptr) в качестве членов класса для других.

6 голосов
/ 21 мая 2009

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

В таких случаях убедитесь, что оператор присваивания (и часто также конструктор копирования) не используется (путем наследования от boost::noncopyable или объявления их закрытыми).

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

4 голосов
/ 21 мая 2009

Да: Верно ли говорить, что объект, содержащий ссылку, нельзя назначать, поскольку ссылка не должна изменяться после инициализации? Мои правила для членов данных:

  • никогда не используйте ссылку, потому что это предотвращает присвоение
  • если ваш класс отвечает за удаление, используйте scoped_ptr boost (который безопаснее, чем auto_ptr)
  • в противном случае используйте указатель или константный указатель
2 голосов
/ 05 февраля 2015

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

2 голосов
/ 21 мая 2009

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

Да. Читаемость вашего кода. Указатель делает более очевидным, что элемент является ссылкой (по иронии судьбы :)), а не отдельным объектом, потому что при его использовании вы должны отменить ссылку на него. Я знаю, что некоторые люди думают, что это старомодно, но я все еще думаю, что это просто предотвращает путаницу и ошибки.

0 голосов
/ 16 декабря 2018

Если я правильно понял вопрос ...

Ссылка в качестве параметра функции вместо указателя: Как вы указали, указатель не дает понять, кому принадлежит очистка / инициализация указателя. Предпочитайте общую точку, когда вам нужен указатель, она является частью C ++ 11, а слабый_птр, когда достоверность данных не гарантируется в течение всего времени жизни класса, принимающего указанные данные .; также часть C ++ 11. Использование ссылки в качестве параметра функции гарантирует, что ссылка не является нулевой. Чтобы обойти это, вы должны подорвать языковые функции, а нам не нужны свободные кодеры пушек.

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

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

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

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

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

Указатели полезны, если параметр A для B не гарантированно был установлен или мог быть переназначен. И иногда слабый указатель слишком многословен для реализации, и многие люди либо не знают, что такое слабый_птр, либо просто не любят их.

Разорвите этот ответ, пожалуйста:)

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