Причина передачі вказівника за посиланням у C ++?


97

За яких обставин ви хотіли б використовувати такий код в C ++?

void foo(type *&in) {...}

void fii() {
  type *choochoo;
  ...
  foo(choochoo);
}

якщо вам потрібно повернути покажчик - краще використовуйте значення, що повертається
littleadv

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

1
Девід резюмує це досить приємно. Сам вказівник модифікується.
Кріс,

2
Я проходжу цей шлях, якщо буду викликати оператор "Новий" у функції.
NDEthos

Відповіді:


142

Вам потрібно буде передати вказівник за посиланням, якщо вам потрібно змінити вказівник, а не об’єкт, на який вказує вказівник.

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


14
Так схожий на покажчик покажчика, за винятком одного меншого рівня опосередкованості для семантики передачі-посилання?

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

2
@William, ти можеш детальніше це пояснити? Це звучить цікаво. Чи означає це, що користувач цього класу міг отримати вказівник на внутрішній буфер даних і читати / писати в нього, але він не може - наприклад - звільнити цей буфер, використовуючи deleteабо перев'язуючи цей вказівник до якогось іншого місця в пам'яті? Або я помилявся?
BarbaraKwarc

2
@BarbaraKwarc Звичайно. Скажімо, у вас проста реалізація динамічного масиву. Природно, ви перевантажите []оператора. Одним із можливих (і насправді природних) підписів для цього буде const T &operator[](size_t index) const. Але ти міг також мати T &operator[](size_t index). Ви могли б мати обидва одночасно. Останнє дозволить вам робити такі речі myArray[jj] = 42. У той же час ви не надаєте вказівник на свої дані, тому абонент не може возитися з пам'яттю (наприклад, випадково видалити її).

Ой. Я думав, ви говорите про повернення посилання на покажчик (ваше точне формулювання: "повернути покажчик за посиланням", тому що саме про це запитував OP: передача покажчиків за посиланнями, а не просто за старими посиланнями) як про якийсь вигадливий спосіб надання доступу до читання / запису до буфера, не дозволяючи маніпулювати самим буфером (наприклад, звільняючи його). Повернення простого старого посилання - це інша річ, і я це знаю.
BarbaraKwarc

65

50% програмістів на C ++ люблять встановлювати свої покажчики на нуль після видалення:

template<typename T>    
void moronic_delete(T*& p)
{
    delete p;
    p = nullptr;
}

Без посилання ви змінювали б лише локальну копію вказівника, не впливаючи на абонента.


19
Мені подобається назва; )
4pie0

2
Я буду торгуватися по-дурному, щоб знайти відповідь: чому це називається дебільним видаленням? Що я пропускаю?
Sammaron

1
@Sammaron Якщо ви подивитесь на історію, раніше викликався шаблон функції paranoid_delete, але Щеня перейменував його на moronic_delete. Ймовірно, він належить до інших 50%;) У будь-якому випадку, коротка відповідь полягає в тому, що вказівники налаштувань на нуль після deleteмайже не бувають корисними (оскільки вони в будь-якому випадку повинні вийти за межі сфери дії) і часто заважає виявленню помилок "використання після безкоштовного".
fredoverflow

@fredoverflow Я розумію вашу відповідь, але, здається, @Puppy вважає, що deleteце взагалі дурість. Щеня, я кілька разів гуглив, я не розумію, чому видалення абсолютно марне (можливо, я теж жахливий гугл;)). Ви можете пояснити трохи більше або надати посилання?
Sammaron

@Sammaron, у C ++ тепер є "розумні вказівники", які інкапсулюють звичайні вказівники та автоматично викликають "видалення" для вас, коли вони виходять за межі області дії. Розумні вказівники переважно віддають перевагу німим вказівникам у стилі С, оскільки вони повністю усувають цю проблему.
tjwrona1992

14

Відповідь Девіда правильна, але якщо вона все ж трохи абстрактна, ось два приклади:

  1. Можливо, ви захочете обнулити всі звільнені вказівники, щоб раніше виявити проблеми з пам'яттю. Ви б виконали C-стиль:

    void freeAndZero(void** ptr)
    {
        free(*ptr);
        *ptr = 0;
    }
    
    void* ptr = malloc(...);
    
    ...
    
    freeAndZero(&ptr);

    У C ++, щоб зробити те ж саме, ви можете зробити:

    template<class T> void freeAndZero(T* &ptr)
    {
        delete ptr;
        ptr = 0;
    }
    
    int* ptr = new int;
    
    ...
    
    freeAndZero(ptr);
  2. При роботі зі зв’язаними списками - часто просто представляються як вказівники на наступний вузол:

    struct Node
    {
        value_t value;
        Node* next;
    };

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

    void insert(Node* &list)
    {
        ...
        if(!list) list = new Node(...);
        ...
    }

У цьому питанні є приклад .


8

Мені доводилося використовувати такий код, щоб надавати функції для виділення пам'яті в переданий покажчик та повернення його розміру, оскільки моя компанія "об'єкт" мені за допомогою STL

 int iSizeOfArray(int* &piArray) {
    piArray = new int[iNumberOfElements];
    ...
    return iNumberOfElements;
 }

Це не приємно, але вказівник повинен передаватися за посиланням (або використовувати подвійний вказівник). Якщо ні, пам'ять виділяється локальній копії вказівника, якщо вона передається за значенням, що призводить до витоку пам'яті.


2

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

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


Чому потік? Щось не так із написаним мною? Потрібно пояснити? : P
BarbaraKwarc

0

Інша ситуація, коли це вам може знадобитися, це якщо у вас є колекція покажчиків stl і ви хочете змінити їх за допомогою алгоритму stl. Приклад for_each в c ++ 98.

struct Storage {
  typedef std::list<Object*> ObjectList;
  ObjectList objects;

  void change() {
    typedef void (*ChangeFunctionType)(Object*&);
    std::for_each<ObjectList::iterator, ChangeFunctionType>
                 (objects.begin(), objects.end(), &Storage::changeObject);
  }

  static void changeObject(Object*& item) {
    delete item;
    item = 0;
    if (someCondition) item = new Object();
  } 

};

В іншому випадку, якщо ви використовуєте підпис changeObject (Object * item), у вас є копія вказівника, а не оригінальний.


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