Чи є переваги проходження вказівника над проходженням посиланням на C ++?


225

Які переваги проходження за вказівником над проходженням за посиланням в C ++?

Останнім часом я бачив ряд прикладів, які вибирали аргументи функції передачі вказівниками замість передачі посилання. Чи є користь від цього?

Приклад:

func(SPRITE *x);

із закликом

func(&mySprite);

vs.

func(SPRITE &x);

із закликом

func(mySprite);

Не забудьте newстворити покажчик і виникаючі проблеми власності.
Мартін Йорк

Відповіді:


216

Покажчик може приймати параметр NULL, опорний параметр не може. Якщо є ймовірність, що ви можете передати "жоден об'єкт", тоді використовуйте вказівник замість посилання.

Крім того, проходження повз покажчик дозволяє чітко побачити на сайті виклику, чи передається об'єкт за значенням або посиланням:

// Is mySprite passed by value or by reference?  You can't tell 
// without looking at the definition of func()
func(mySprite);

// func2 passes "by pointer" - no need to look up function definition
func2(&mySprite);

18
Неповна відповідь. Використання покажчиків не дозволяє авторизувати використання тимчасових / рекламованих об'єктів, а також використання загостреного об'єкта як об'єктів, схожих на стек. І це дозволить припустити, що аргументом може бути NULL, коли в більшості випадків значення NULL повинно бути заборонено. Прочитайте відповідь лампа, щоб отримати повну відповідь.
paercebal

Другий виклик функції раніше коментувався func2 passes by reference. Хоча я ціную, що ти мав на увазі, що він проходить "посиланням" з точки зору високого рівня, реалізований шляхом передачі вказівника на перспективу рівня коду, це було дуже заплутано (див. Stackoverflow.com/questions/13382356/… ).
Гонки легкості по орбіті

Я просто не купую цього. Так, ви передаєте вказівник, тому він повинен бути вихідним параметром, тому що на що вказується не може бути const?
deworde

Хіба у нас немає цього прохідного посилання на С? Я використовую останню версію кодового блоку (mingw) і підбираю проект C. Ще проходить посилання (func (int & a)) працює. Або він доступний у C99 або C11 і далі?
Джон Уілок

1
@JonWheelock: Ні, C взагалі не має прохідних посилань. func(int& a)не діє в жодній версії стандарту. Ви, ймовірно, випадково збираєте свої файли як C ++.
Адам Розенфілд

245

Проходження по вказівнику

  • Абонент повинен взяти адресу -> не прозоро
  • Значення 0 може бути передбачене для значення nothing. Це можна використовувати для надання необов'язкових аргументів.

Перейти за посиланням

  • Абонент просто передає об'єкт -> прозоро. Потрібно використовувати для перевантаження оператора, оскільки перевантаження для типів вказівників неможливе (покажчики вбудовані в типи). Таким чином, ви не можете string s = &str1 + &str2;використовувати вказівники.
  • Неможливо 0 значень -> Викликана функція не повинна їх перевіряти
  • Посилання на const також приймає тимчаси:, void f(const T& t); ... f(T(a, b, c));покажчики не можна використовувати так, оскільки ви не можете взяти адресу тимчасової.
  • І останнє, але не менш важливе, посилання простіше використовувати -> менше шансів на помилки.

7
Проходження повз покажчика також викликає "Передано право власності чи ні?" питання. Це не стосується посилань.
Фріріх Раабе

43
Я не згоден з "меншим шансом на помилки". Під час огляду сайту виклику і читач бачить "foo (& s)", відразу зрозуміло, що s може бути змінено. Коли ви читаєте "foo (s)", то зовсім не зрозуміло, чи можна змінити s. Це головне джерело помилок. Можливо, існує менше шансів на певний клас помилок, але в цілому, проходження посилань - це величезне джерело помилок.
Вільям Перселл

25
Що ви маєте на увазі під "прозорим"?
Gbert90

2
@ Gbert90, якщо ви бачите foo (& a) на сайті виклику, ви знаєте, що foo () приймає тип вказівника. Якщо ви бачите foo (a), ви не знаєте, чи посилається він.
Майкл Дж. Давенпорт

3
@ MichaelJ.Davenport - у вашому поясненні ви пропонуєте "прозорий", щоб щось означало по лінії "очевидно, що абонент передає вказівник, але не очевидно, що абонент передає посилання". У публікації Йоганнеса він говорить: "Проїжджаючи вказівником - Caller повинен взяти адресу -> не прозоро" та "Pass by reference - Caller просто передає об'єкт -> transparent" - що майже протилежне тому, що ви говорите . Я думаю, що питання Gbert90 "Що ви маєте на увазі під" прозорим "", все ще залишається в силі.
Happy Green Kid Naps

66

Мені подобаються міркування статтею від "cplusplus.com:"

  1. Передайте значення, коли функція не бажає змінювати параметр, а значення легко копіювати (ints, double, char, bool тощо) прості типи. Std :: string, std :: vector та всі інші STL контейнери НЕ прості типи.)

  2. Пройти повз const покажчик, коли значення дорого копіюється І функція не хоче змінювати значення, вказане на AND NULL, є дійсним, очікуваним значенням, яке обробляє функція.

  3. Пройдіть повз покажчик non-const, коли значення копіюється дорого І функція хоче змінити значення, вказане на AND NULL, є дійсним, очікуваним значенням, яке обробляє функція.

  4. Перейти через посилання const, коли значення дорого копіюється І функція не хоче змінювати значення, на яке посилається AND NULL, не було б дійсним значенням, якби замість цього використовувався покажчик.

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

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

http://www.cplusplus.com/articles/z6vU7k9E/

Що я беру з цього, це те, що основна різниця між вибором використання вказівника або еталонного параметра полягає в тому, якщо NULL є прийнятним значенням. Це воно.

Незалежно від того, чи є значення вхідним, вихідним, змінним і т. Д., Повинно бути в документації / коментарях щодо функції.


Так, для мене ключові проблеми, пов'язані з NULL, є тут. Thx для цитування ..
binaryguy

62

У творі Аллена Голуба "Досить мотузки, щоб забити себе в ногу" перелічено наступні 2 правила:

120. Reference arguments should always be `const`
121. Never use references as outputs, use pointers

Він перераховує кілька причин, чому посилання додані до C ++:

  • вони необхідні для визначення конструкторів копій
  • вони необхідні для перевантажень оператора
  • const посилання дозволяють мати семантику прохідних значень, уникаючи копії

Його головна суть полягає в тому, що посилання не повинні використовуватися як параметри «виводу», оскільки на сайті виклику немає вказівок на те, чи є параметр посилальним чи значенням. Тому його правило - використовувати лише constпосилання як аргументи.

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


8
Моя позиція в цьому аргументі полягає в тому, що якщо ім'я функції робить це абсолютно очевидним, не перевіряючи документи, що парам буде змінено, тоді посилання на безконтактність нормально. Тож особисто я дозволяю "getDetails (DetailStruct & результат)". Вказівник там підвищує потворну можливість введення NULL.
Стів Джессоп

3
Це вводить в оману. Навіть якщо деякі не люблять посилань, вони є важливою частиною мови і їх слід використовувати. Цей рядок міркувань начебто не використовуйте шаблони. Ви завжди можете використовувати контейнери void * для зберігання будь-якого типу. Прочитайте відповідь ламб.
Девід Родрігес - дрибес

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

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

1
Це правило використовується в посібнику зі стилів google c ++: google-styleguide.googlecode.com/svn/trunk/…
Антон Данейко

9

Пояснення до попередніх дописів:


Посилання НЕ є гарантією отримання ненульового вказівника. (Хоча ми часто ставимось до них як до таких.)

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

bool test( int & a)
{
  return (&a) == (int *) NULL;
}

int
main()
{
  int * i = (int *)NULL;
  cout << ( test(*i) ) << endl;
};

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

Раптом з’являється світ різниці між футом (бар BAR) і футом (BAR & bar) . (Запускається автоматична операція побітового копіювання. Виділення в деструкторі викликається двічі.)

На щастя, сучасні компілятори підберуть це подвійне розташування того ж покажчика. 15 років тому вони цього не зробили. (У розділі gcc / g ++ використовуйте setenv MALLOC_CHECK_ 0, щоб переглянути старі способи.) У результаті DEC UNIX виникла однакова пам'ять, виділена двом різним об'єктам. Там багато веселої налагодження ...


Більш практично:

  • Посилання приховують, що ви змінюєте дані, що зберігаються десь в іншому місці.
  • Легко сплутати Посилання з Копійованим об'єктом.
  • Покажчики роблять це очевидним!

16
це не проблема функції або посилань. ви порушуєте мовні правила. перенаправлення нульового вказівника само по собі - це вже невизначена поведінка. "Посилання НЕ є гарантією отримання ненульового вказівника.": Сам стандарт каже, що вони є. інші способи становлять невизначену поведінку.
Йоханнес Шауб - ліб

1
Я згоден з litb. Хоча правда, код, який ви нам показуєте, є більш диверсійним, ніж будь-що інше. Існують способи саботажу будь-чого, включаючи як позначення "посилання", так і "вказівник".
paercebal

1
Я сказав, що це "виведи тебе за поганий код". У цьому ж ключі ви також можете мати i = новий FOO; видалити i; тест (* i); Ще одне (на жаль, поширене) звисання вказівника / посилання.
Mr.Ree

1
Це насправді не перенаправлення NULL, а скоріше використання цього dereferenced (null) об’єкта. Таким чином, насправді немає різниці (крім синтаксису) між вказівниками та посиланнями з точки зору реалізації мови. Користувачі мають різні очікування.
Mr.Ree

2
Незалежно від того, що ви робите з поверненою посиланням, у момент, коли ви говорите *i, у вашій програмі не визначена поведінка. Наприклад, компілятор може бачити цей код і припускати "ОК, цей код має невизначену поведінку у всіх кодових шляхах, тому вся ця функція повинна бути недосяжною". Тоді буде прийнято вважати, що всі гілки, що ведуть до цієї функції, не беруться. Це регулярно проводиться оптимізація.
Девід Стоун

5

Більшість відповідей тут не стосуються притаманної неоднозначності щодо того, щоб мати необроблений покажчик у підписі функції, з точки зору вираження намірів. Проблеми такі:

  • Абонент не знає, чи вказує вказівник на окремі об'єкти, або на початок "масиву" об'єктів.

  • Абонент не знає, чи "вказівник" володіє пам'яттю, на яку він вказує. IE, чи повинна функція звільнити пам'ять. ( foo(new int)- Це витік пам'яті?).

  • Абонент не знає, чи nullptrможна безпечно перейти до функції.

Усі ці проблеми вирішуються посиланнями:

  • Посилання завжди стосуються одного об'єкта.

  • Посилання ніколи не володіють пам’яттю, на яку вони посилаються, вони є лише переглядом пам’яті.

  • Посилання не можуть бути нульовими.

Це робить посилання набагато кращим кандидатом для загального використання. Однак посилання не є ідеальними - є кілька основних проблем, які слід врахувати.

  • Відсутня явна непрямість. Це не є проблемою із необробленим покажчиком, оскільки ми повинні використовувати &оператор, щоб показати, що ми дійсно передаємо вказівник. Наприклад, int a = 5; foo(a);тут зовсім не ясно, що a передається через посилання і може бути змінено.
  • Зменшення. Ця слабкість покажчиків також може бути сильною, коли ми насправді хочемо, щоб наші посилання були нульовими. Вважаючи, що std::optional<T&>це не дійсно (з поважних причин), вказівники дають нам ту нульовість, яку ви хочете.

Тож здається, що коли ми хочемо нульової посилання з явним непрямим впливом, ми повинні досягти T*права? Неправильно!

Абстракції

У нашому відчаї до мінливості ми можемо досягти T*і просто ігнорувати всі перелічені раніше недоліки та смислові неоднозначності. Натомість нам слід досягти того, що C ++ найкраще робить: абстракція. Якщо ми просто пишемо клас, який обертається навколо вказівника, ми набуваємо виразності, а також зворотності та явної непрямості.

template <typename T>
struct optional_ref {
  optional_ref() : ptr(nullptr) {}
  optional_ref(T* t) : ptr(t) {}
  optional_ref(std::nullptr_t) : ptr(nullptr) {}

  T& get() const {
    return *ptr;
  }

  explicit operator bool() const {
    return bool(ptr);
  }

private:
  T* ptr;
};

Це найпростіший інтерфейс, який я міг би придумати, але він робить свою роботу ефективно. Це дозволяє ініціалізувати посилання, перевіряти, чи існує якесь значення, і отримувати доступ до значення. Ми можемо використовувати його так:

void foo(optional_ref<int> x) {
  if (x) {
    auto y = x.get();
    // use y here
  }
}

int x = 5;
foo(&x); // explicit indirection here
foo(nullptr); // nullability

Ми досягли своїх цілей! Давайте тепер подивимося на переваги, порівняно із сирим покажчиком.

  • Інтерфейс чітко показує, що посилання має стосуватися лише одного об’єкта.
  • Очевидно, що вона не володіє пам'яттю, на яку посилається, оскільки не має визначеного користувачем деструктора і не має способу видалення пам'яті.
  • Користувач знає, що nullptrможе бути переданий, оскільки автор функції явно запитує анoptional_ref

Звідси ми можемо зробити інтерфейс більш складним, наприклад, додавання операторів рівності, монадики get_orта mapінтерфейсу, методу, який отримує значення або кидає виняток, constexprпідтримку. Це ти можеш зробити.

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


3

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

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


1
Це і перевага, і недолік. Багато API використовують вказівники NULL, щоб означати щось корисне (тобто NULL timespec чекає вічно, тоді як значення означає довго чекати).
Грег Роджерс

1
@Brian: Я не хочу забирати ніт, але: я б не сказав, що гарантовано отримаю екземпляр при отриманні посилання. Посилання на данглінг все ще можливі, якщо абонент функції відсилає до висячого вказівника, який виклик не може знати.
foraidt

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

Програми, що містять звисаючі посилання, не дійсні на C ++. Тому так, код може припускати, що всі посилання є дійсними.
Конрад Рудольф

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