Причина передавать указатель по ссылке в C ++?

98

При каких обстоятельствах вы хотели бы использовать такой код в C ++?

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

void fii() {
  type *choochoo;
  ...
  foo(choochoo);
}
Мэтью Хогган
источник
если вам нужно вернуть указатель - лучше используйте возвращаемое значение
littleadv
6
Вы можете объяснить почему? Этот отзыв не очень полезен. У меня вполне законный вопрос. В настоящее время это используется в производственном коде. Я просто не совсем понимаю, почему.
Мэтью Хогган
1
Дэвид резюмирует это довольно хорошо. Сам указатель модифицируется.
Крис
2
Я прохожу этот путь, если буду вызывать в функции оператор «Новый».
NDEthos

Ответы:

143

Вы можете передать указатель по ссылке, если вам нужно изменить указатель, а не объект, на который он указывает.

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

Дэвид З.
источник
14
Так похоже на указатель на указатель, за исключением того, что на один уровень косвенности для семантики передачи по ссылке меньше?
Хочу добавить, что иногда бывает нужно вернуть указатель по ссылке. Например, если у вас есть класс, содержащий данные в динамически выделяемом массиве, но вы хотите предоставить (непостоянный) доступ к этим данным клиенту. В то же время вы не хотите, чтобы клиент мог управлять памятью через указатель.
2
@ Уильям, ты можешь подробнее рассказать об этом? Звучит интересно. Означает ли это, что пользователь этого класса может получить указатель на внутренний буфер данных и читать / писать в него, но он не может, например, освободить этот буфер, используя deleteили повторно привязав этот указатель к какому-либо другому месту в памяти? Или я ошибся?
BarbaraKwarc
2
@BarbaraKwarc Конечно. Допустим, у вас есть простая реализация динамического массива. Естественно вы перегрузите []оператора. Одна из возможных (и фактически естественных) подписей для этого была бы const T &operator[](size_t index) const. Но вы тоже могли T &operator[](size_t index). Вы могли иметь и то и другое одновременно. Последний позволит вам делать такие вещи, как myArray[jj] = 42. В то же время вы не предоставляете указатель на свои данные, поэтому вызывающий не может вмешиваться в память (например, случайно удалить ее).
Ой. Я думал, вы говорите о возврате ссылки на указатель (ваша точная формулировка: «вернуть указатель по ссылке», потому что именно об этом и спрашивал OP: передача указателей по ссылкам, а не только по простым старым ссылкам) как о некоем причудливом способе предоставление доступа для чтения / записи к буферу без разрешения манипулировать самим буфером (например, освобождать его). Другое дело - вернуть простую старую ссылку, и я это знаю.
BarbaraKwarc
65

50% программистов на C ++ любят устанавливать для своих указателей значение null после удаления:

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

Без ссылки вы изменили бы только локальную копию указателя, не затрагивая вызывающего.

fredoverflow
источник
19
Мне нравится название; )
4pie0
2
Я променяю глупость на то, чтобы выяснить ответ: почему это называется дебильным удалением? Что мне не хватает?
Sammaron
1
@Sammaron Если вы посмотрите историю, то раньше вызывался шаблон функции paranoid_delete, но Puppy переименовал его в moronic_delete. Он, вероятно, принадлежит к другим 50%;) В любом случае, краткий ответ заключается в том, что указатели настроек на null после deleteпочти никогда не используются (потому что они должны выходить за рамки, в любом случае) и часто затрудняют обнаружение ошибок «использования после освобождения».
fredoverflow 07
@fredoverflow Я понимаю ваш ответ, но @Puppy, кажется, deleteв целом считает глупым. Щенок, я погуглил, я не понимаю, почему удаление совершенно бесполезно (может, я тоже ужасный гуглер;)). Не могли бы вы пояснить еще немного или дать ссылку?
Sammaron
@Sammaron, в C ++ теперь есть "умные указатели", которые инкапсулируют обычные указатели и автоматически вызывают "удаление", когда они выходят за пределы области видимости. Умные указатели гораздо предпочтительнее немых указателей в стиле 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;
 }

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

математик1975
источник
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