Чи слід віддати перевагу вказівникам чи посиланням у даних про учасників?


138

Це спрощений приклад для ілюстрації питання:

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 в даних членів класу, що володіє вказівником, і передаю об'єкт навколо як орієнтир.

Як правило, вказівник у даних членів використовую лише тоді, коли вказівник може бути нульовим або мінятись. Чи є інші причини віддати перевагу покажчикам над посиланнями для членів даних?

Чи правда сказати, що об'єкт, що містить посилання, не повинен бути присвоєний, оскільки посилання не слід змінювати, коли він ініціалізується?


", але копія та призначення за замовчуванням також погані для покажчиків": Це не те саме: Вказівник можна змінювати коли завгодно, якщо це не const. Посилання, як правило, завжди const! (Ви побачите це, якщо спробуєте змінити свого члена на "A & const a;". Компілятор (принаймні GCC) попередить вас, що посилання є const навіть без ключового слова "const".
mmmmmmmm

Основна проблема з цим полягає в тому, що якщо хтось робить B b (A ()), то ви злякані, тому що у вас закінчується звисаюча посилання.
log0

Відповіді:


67

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

Приклад проблем:

  • ви змушені ініціалізувати посилання у списку ініціалізаторів кожного конструктора: немає ніякого способу розподілити цю ініціалізацію на іншу функцію ( доти, доки C ++ 0x, все-таки редагуйте: C ++ тепер має делегуючі конструктори )
  • посилання не може бути відновним або недійсним. Це може бути перевагою, але якщо код коли-небудь потребує змін, щоб дозволити перезавантаження або для того, щоб член був недійсним, все використання члена потрібно змінити
  • на відміну від членів вказівника, посилання не можуть бути легко замінені розумними покажчиками чи ітераторами, як може знадобитися рефакторинг
  • Щоразу, коли використовується посилання, воно виглядає як тип значення ( .оператор тощо), але поводиться як вказівник (може зависати) - так, наприклад, Посібник із стилів Google відбиває його

66
Усі речі, про які ви згадали, - це хороші речі, яких слід уникати, тому якщо посилання допомагають у цьому - вони хороші, а не погані. Ініціалізаційний список - найкраще місце для введення даних. Дуже часто доводиться приховувати оператора присвоєння, з посиланнями вам не доведеться. "Неможливо відскочити" - добре, повторне використання змінних - погана ідея.
Микола Голубєв

4
@Mykola: Я з вами згоден. Я вважаю за краще, щоб члени були ініціалізовані в ініціалізаторських списках, я уникаю нульових покажчиків, і це, звичайно, не добре, щоб змінна міняла своє значення. Там, де наші думки відрізняються, це те, що мені не потрібно або хочу, щоб компілятор це застосував для мене. Це не робить уроки простішими в написанні, я не стикався з помилками в цій області, які б потрапили, і час від часу ціную гнучкість, яку отримую від коду, який постійно використовує члени вказівника (розумні покажчики, де це доречно).
Джеймс Хопкін

Я думаю, що не потрібно приховувати призначення - це неправдивий аргумент, оскільки вам не доведеться приховувати призначення, якщо клас містить вказівник, що він все одно не несе відповідальності за видалення - це буде за замовчуванням. Я думаю, що це найкраща відповідь, оскільки це дає вагомі причини, чому вказівники повинні віддавати перевагу над посиланнями в даних членів.
markh44

4
Джеймс, це не те, що він полегшує написання коду, це полегшує читання коду. З посиланням на учасника даних вам ніколи не доведеться задаватися питанням, чи може це бути недійсним у точці використання. Це означає, що ви можете подивитися на код з меншим необхідним контекстом.
Лен Холгейт

4
Це робить тестування блоків і їх макет набагато складніше, неможливо майже залежно від того, скільки використовуються, у поєднанні з «вибухом графіку впливу» є ІМХО достатньо вагомих причин ніколи не використовувати їх.
Кріс Хуанг-Лівер

156

Моє власне правило:

  • Використовуйте контрольний член, коли ви хочете, щоб життя вашого об'єкта залежало від життя інших об'єктів : це явний спосіб сказати, що ви не дозволяєте об'єкту бути живим без дійсного примірника іншого класу - через ні призначення та зобов'язання отримати ініціалізацію посилань через конструктор. Це гарний спосіб спроектувати свій клас, не припускаючи нічого про те, чи є, наприклад, член чи інший клас. Ви тільки припускаєте, що їх життя безпосередньо пов'язане з іншими прикладами. Це дозволяє згодом змінити спосіб використання вашого екземпляра класу (з новим, як локальний екземпляр, як член класу, згенерований пулом пам'яті в менеджері тощо).
  • Використовуйте вказівник в інших випадках : Коли ви хочете, щоб член був змінений пізніше, використовуйте вказівник або const вказівник, щоб переконатися, що він читав лише вказаний екземпляр. Якщо цей тип передбачається скопіювати, ви ніяк не можете використовувати посилання. Іноді також потрібно ініціалізувати член після виклику спеціальної функції (наприклад, init ()), і тоді у вас просто немає іншого вибору, крім використання покажчика. АЛЕ: використовуйте твердження у всіх функціях вашого члена, щоб швидко виявити неправильний стан вказівника!
  • У тих випадках, коли ви хочете, щоб термін експлуатації об'єкта залежав від терміну експлуатації зовнішнього об'єкта, а також вам потрібно скопіювати цей тип, тоді використовуйте елементи вказівника, але довідковий аргумент в конструкторі. Таким чином ви вказуєте на конструкції, що залежить термін експлуатації цього об'єкта. протягом життя аргументу, АЛЕ реалізація використовує покажчики, які все ще можна скопіювати. Поки ці учасники змінюються лише копією, а ваш тип не має конструктора за замовчуванням, тип повинен виконати обидві цілі.

1
Я думаю, що Ваші та Микола - це правильні відповіді та сприяють програмуванню без показів (читайте менше покажчиків).
Аміт

37

Об'єкти рідко повинні дозволяти призначати та інші речі, як порівняння. Якщо ви розглядаєте якусь бізнес-модель з такими об'єктами, як "Відділ", "Співробітник", "Директор", важко уявити випадок, коли одного працівника буде призначено на іншого.

Тож для бізнес-об’єктів дуже добре описувати відносини один до одного і один на багато як посилання, а не покажчики.

І, ймовірно, це нормально, щоб описати співвідношення один чи нуль як покажчик.

Тож ніякий чинник "ми не можемо призначити".
Багато програмістів просто звикають з покажчиками, і тому вони знайдуть будь-який аргумент, щоб уникнути використання посилань.

Якщо вказівник є членом, ви змусите вас або члена вашої команди перевірити вказівник знову і знову перед використанням, з коментарем «на всякий випадок». Якщо вказівник може бути нульовим, то, можливо, вказівник використовується як вид прапора, що погано, оскільки кожен об'єкт повинен грати свою власну роль.


2
+1: Те саме, що я пережив. Лише деякі загальні класи зберігання даних потребують assignemtn та copy-c'tor. А для більш високих рівнів бізнес-об'єктів повинні існувати інші методи копіювання, які також обробляють речі, такі як "не копіюйте унікальні поля" та "додайте до іншого батьківського" тощо. Отже, ви використовуєте рамку високого рівня для копіювання бізнес-об'єктів і заборонити призначення низького рівня.
мммммммм

2
+1: Деякі пункти згоди: довідкові члени, що перешкоджають визначенню оператора призначення, не є важливим аргументом. Як ви правильно констатуєте інший спосіб, не всі об’єкти мають значення семантики. Я також погоджуюся, що ми не хочемо, щоб "if (p)" було розкидано по всьому коду без логічної причини. Але правильний підхід до цього відбувається через класні інваріанти: чітко визначений клас не залишить місця для сумнівів у тому, чи можуть члени бути недійсними. Якщо будь-який вказівник може бути нульовим у коді, я очікую, що це добре прокоментується.
Джеймс Хопкін

@JamesHopkin Я погоджуюся, що добре розроблені класи можуть ефективно використовувати покажчики. Крім захисту від NULL, посилання також гарантують, що ваша посилання була ініціалізована (покажчик не потрібно ініціалізувати, тому він може вказувати на будь-що інше). Посилання також розповідають про право власності - а саме на те, що об’єкт не є членом. Якщо ви послідовно використовуєте посилання для зведених членів, у вас буде дуже висока впевненість, що члени покажчика є складовими об'єктами (хоча не так багато випадків, коли складений член представлений вказівником).
weberc2

11

7
Я читав це, але вважав, що це говорить про прохідні параметри, а не про дані членів. "Зазвичай посилання з'являються на шкірі предмета, а вказівники - на внутрішній стороні."
markh44

Так, я не думаю, що це корисна порада в контексті посилань на членів.
Тім Ангус

6

У кількох важливих випадках присвоєння просто не потрібне. Це часто легкі обгортки алгоритму, які полегшують обчислення, не виходячи із сфери застосування. Такі об'єкти є головними кандидатами для довідкових членів, оскільки ви можете бути впевнені, що вони завжди мають дійсне посилання і ніколи не потрібно їх копіювати.

У таких випадках переконайтеся, що оператор призначення (а часто також і конструктор копій) робить непридатним для використання (успадковуючи boost::noncopyableабо оголошуючи їх приватними).

Однак, як уже прокоментували користувачі pts, те ж саме не стосується більшості інших об'єктів. Тут використання опорних членів може бути величезною проблемою, і її взагалі слід уникати.


6

Оскільки, здається, всі роздають загальні правила, я запропоную два:

  • Ніколи, ніколи не використовуйте посилання як члени класу. Я ніколи цього не робив у своєму власному коді (крім того, щоб довести собі, що я мав рацію в цьому правилі) і не можу уявити собі випадок, коли б це робив. Семантика занадто заплутана, і це дійсно не те, для чого були розроблені посилання.

  • Завжди завжди використовуйте посилання при передачі параметрів функцій, за винятком основних типів або коли алгоритм вимагає копії.

Ці правила прості, і витримували мене добре. Я залишаю правила використання розумних покажчиків (але, будь ласка, не auto_ptr) як членів класу для інших.


2
Не дуже згодні з першим, але розбіжність не є важливим для обґрунтування. +1
Девід Родрігес - дрибес

(Ми, мабуть, втрачаємо цей аргумент із добрими виборцями ТА, але)
Джеймс Хопкін

2
Я проголосував за те, що "Ніколи, ніколи не використовуйте посилання як члени класу", і наступне обгрунтування не відповідає мені. Як пояснено у моїй відповіді (і прокоментуйте верхню відповідь) та з власного досвіду, бувають випадки, коли це просто гарна річ. Я думав, як ти, поки мене не прийняли на роботу в компанію, де вони добре використовували її, і мені стало зрозуміло, що є випадки, коли це допомагає. Насправді я думаю, що справжня проблема полягає більше в синтаксисі та прихованому підтексті, завдяки чому використання посилань як членів вимагає від програміста зрозуміти, що він робить.
Клайм

1
Ніл> Я згоден, але це не "думка" змусило мене проголосувати, це твердження "ніколи". Оскільки сперечатися тут все одно не є корисним, я скасую свій голос проти.
Клайм

2
Цю відповідь можна було б покращити, пояснивши, що щодо семантики референтних членів бентежить. Це обґрунтування важливе, оскільки вказівники на його погляд здаються більш семантично неоднозначними (вони можуть не бути ініціалізованими, і вони нічого не говорять про право власності на базовий об'єкт).
weberc2

4

Так для: Чи правда говорити, що об'єкт, що містить посилання, не повинен бути присвоєний, оскільки посилання не слід змінювати, коли він ініціалізується?

Мої правила щодо членів даних:

  • ніколи не використовуйте посилання, оскільки це перешкоджає призначенню
  • якщо ваш клас відповідає за видалення, використовуйте boped's scoped_ptr (що безпечніше, ніж auto_ptr)
  • в іншому випадку використовуйте вказівник або const вказівник

2
Я навіть не можу назвати випадок, коли мені знадобиться призначення об'єкта бізнесу.
Микола Голубєв

1
+1: Я просто додам бути обережним з членами auto_ptr - будь-який клас, який має їх, повинен мати чітко визначені конструкції присвоєння та копіювання (згенеровані, навряд чи, те, що потрібно)
Джеймс Хопкін

auto_ptr також запобігає (розумному) призначенню.

2
@Mykola: Я також переконався, що 98% об'єктів мого бізнесу не потребують присвоєння чи копіювання. Я запобігаю цьому невеликим макросом, який я додаю до заголовків тих класів, який робить копії c'tors та operator = () приватними без реалізації. Тож у 98% об’єктів я також використовую посилання, і це безпечно.
мммммммм

1
Посилання як члени можна чітко заявити, що життя екземпляра класу безпосередньо залежить від життя екземплярів інших класів (або того ж). "Ніколи не використовуйте посилання, тому що це перешкоджає призначенню" - це припущення, що всі класи повинні допускати призначення. Це як припускати, що всі класи повинні дозволити операцію копіювання ...
Klaim

2

Як правило, вказівник у даних членів використовую лише тоді, коли вказівник може бути нульовим або мінятись. Чи є інші причини віддати перевагу покажчикам над посиланнями для членів даних?

Так. Читання вашого коду. Вказівник робить більш очевидним, що член - це посилання (за іронією долі :)), а не вміст, що міститься, тому що, коли ви користуєтесь ним, ви повинні де-посилатися на нього. Я знаю, що деякі люди думають, що це старомодно, але я все ще думаю, що це просто запобігає плутанині та помилкам.


1
Лакос також аргументує це в "Дизайні програмного забезпечення великого масштабу".
Йохан Котлінський

Я вважаю, що Code Complete виступає і за це, і я не проти. Але це не надто переконливо ...
markh44

2

Я раджу проти учасників довідкових даних, оскільки ви ніколи не знаєте, хто буде походити з вашого класу і що вони можуть захотіти робити. Вони можуть не захотіти використовувати посилається об'єкт, але, будучи посиланням, ви змусили їх надати дійсний об'єкт. Я зробив це для себе досить, щоб перестати використовувати члени референтних даних.


0

Якщо я правильно розумію питання ...

Посилання як параметр функції замість вказівника: Як ви вказали, вказівник не дає зрозуміти, кому належить очищення / ініціалізація вказівника. Віддайте перевагу спільній точці, коли потрібно вказівник, вона є частиною C ++ 11 і слабкою_птр, коли достовірність даних не гарантується протягом життя класу, який приймає вказані дані; також частина C ++ 11. Використання посилання як функціонального параметра гарантує, що посилання не є нульовим. Вам потрібно підірвати мовні функції, щоб обійти це, і нас не хвилюють вільні кодерні гармати.

Довідкова змінна члена: Як і вище щодо достовірності даних. Це гарантує, що вказані на дані, на які тоді посилаються, є дійсними.

Перенесення відповідальності за змінну дійсність на більш ранню точку коду не тільки очищає пізніший код (клас А у вашому прикладі), але й дає зрозуміти особі, яка використовує.

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

І на всякий випадок (що є малоймовірним кришкою, оскільки це не має сенсу в контексті ваших кодів), інша альтернатива - використовувати не посилається, не вказівний параметр A, скопіює A і зробить парадигму марною - я насправді не думаю, що ви маєте на увазі це як альтернативу.

Також це гарантує, що ви не зможете змінити посилання на дані, де вказівник можна змінити. Вказівник const буде працювати, лише якщо посилання / вказівник на дані не змінено.

Покажчики корисні, якщо параметр A для B не був гарантовано встановлений або міг бути переназначений. І іноді слабкий покажчик є занадто багатослівним для реалізації, і велика кількість людей або не знає, що таке слабкий_ptr, або просто не любить їх.

Відірвіть цю відповідь, будь ласка :)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.