Alasan Meneruskan Pointer dengan Referensi di C ++?


98

Dalam keadaan apa Anda ingin menggunakan kode seperti ini di c ++?

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

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

jika Anda perlu mengembalikan pointer - lebih baik gunakan nilai kembali
littleadv

6
Bisakah Anda menjelaskan mengapa? Pujian ini tidak terlalu membantu. Pertanyaan saya cukup sah. Ini sedang digunakan dalam kode produksi. Saya hanya tidak sepenuhnya mengerti mengapa.
Matthew Hoggan

1
David menyimpulkannya dengan cukup baik. Penunjuk itu sendiri sedang dimodifikasi.
chris

2
Saya melewati jalan ini jika saya akan memanggil operator "Baru" dalam fungsi tersebut.
NDEthos

Jawaban:


143

Anda ingin meneruskan pointer dengan referensi jika Anda memiliki kebutuhan untuk memodifikasi pointer daripada objek yang ditunjuk pointer.

Ini mirip dengan mengapa penunjuk ganda digunakan; menggunakan referensi ke pointer sedikit lebih aman daripada menggunakan pointer.


14
Begitu mirip dengan pointer dari sebuah pointer, kecuali satu tingkat tipuan yang lebih sedikit untuk semantik pass-by-reference?

Saya ingin menambahkan bahwa terkadang Anda juga ingin mengembalikan pointer dengan referensi. Misalnya, jika Anda memiliki kelas yang menyimpan data dalam larik yang dialokasikan secara dinamis, tetapi Anda ingin memberikan akses (tidak konstan) ke data ini ke klien. Pada saat yang sama, Anda tidak ingin klien dapat memanipulasi memori melalui penunjuk.

2
@William bisakah kamu menjelaskannya? Kedengarannya menarik. Apakah ini berarti bahwa pengguna kelas itu bisa mendapatkan pointer ke buffer data internal dan membaca / menulis ke sana, tetapi tidak bisa - misalnya - membebaskan buffer itu menggunakan deleteatau memasang kembali pointer itu ke tempat lain di memori? Atau apakah saya salah?
BarbaraKwarc

2
@Bara_cantik. Katakanlah Anda memiliki implementasi sederhana dari array dinamis. Tentu Anda akan membebani []operator. Salah satu tanda tangan yang mungkin (dan sebenarnya alami) untuk ini adalah const T &operator[](size_t index) const. Tapi Anda juga bisa melakukannya T &operator[](size_t index). Anda bisa mendapatkan keduanya pada saat bersamaan. Yang terakhir akan memungkinkan Anda melakukan hal-hal seperti myArray[jj] = 42. Pada saat yang sama, Anda tidak memberikan penunjuk ke data Anda, sehingga pemanggil tidak dapat mengacaukan memori (misalnya menghapusnya secara tidak sengaja).

Oh. Saya pikir Anda berbicara tentang mengembalikan referensi ke pointer (kata-kata persisnya: "kembalikan pointer dengan referensi", karena itulah yang ditanyakan OP: meneruskan pointer dengan referensi, bukan hanya referensi lama biasa) sebagai cara yang mewah untuk memberikan akses baca / tulis ke buffer tanpa mengizinkan untuk memanipulasi buffer itu sendiri (misalnya, membebaskannya). Mengembalikan referensi lama yang polos adalah hal yang berbeda dan saya tahu itu.
BarbaraKwarc

65

50% programmer C ++ suka mengatur pointer mereka ke null setelah menghapus:

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

Tanpa referensi, Anda hanya akan mengubah salinan lokal penunjuk, tidak memengaruhi pemanggil.


19
Saya suka namanya; )
4pie0

2
Saya akan berdagang terdengar bodoh karena mencari tahu jawabannya: mengapa ini disebut penghapusan yang bodoh? Apa yang saya lewatkan?
Sammaron

1
@Sammaron Jika Anda melihat histori, template fungsi dulu dipanggil paranoid_delete, tapi Puppy menamainya moronic_delete. Dia mungkin termasuk 50% lainnya;) Bagaimanapun, jawaban singkatnya adalah bahwa pengaturan menunjuk ke nol setelah deletehampir tidak pernah berguna (karena mereka harus keluar dari ruang lingkup, bagaimanapun) dan sering menghambat deteksi bug "gunakan setelah gratis".
fredoverflow

@fredoverflow Saya memahami tanggapan Anda, tetapi @Puppy tampaknya deletedianggap bodoh secara umum. Anak anjing, saya melakukan beberapa googling, saya tidak mengerti mengapa menghapus sama sekali tidak berguna (mungkin saya juga googler yang buruk;)). Bisakah Anda menjelaskan lebih banyak atau memberikan tautan?
Sammaron

@Sammaron, C ++ memiliki "penunjuk cerdas" sekarang yang merangkum penunjuk biasa dan secara otomatis memanggil "hapus" untuk Anda ketika mereka berada di luar jangkauan. Petunjuk cerdas jauh lebih disukai daripada petunjuk bodoh gaya C karena mereka menghilangkan masalah ini sepenuhnya.
tjwrona1992

14

Jawaban David benar, tetapi jika masih sedikit abstrak, berikut dua contohnya:

  1. Anda mungkin ingin menghapus semua petunjuk yang dibebaskan untuk mengetahui masalah memori sebelumnya. Gaya C yang akan Anda lakukan:

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

    Di C ++ untuk melakukan hal yang sama, Anda dapat melakukan:

    template<class T> void freeAndZero(T* &ptr)
    {
        delete ptr;
        ptr = 0;
    }
    
    int* ptr = new int;
    
    ...
    
    freeAndZero(ptr);
  2. Saat berurusan dengan daftar tertaut - sering kali hanya direpresentasikan sebagai petunjuk ke node berikutnya:

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

    Dalam kasus ini, ketika Anda menyisipkan ke daftar kosong Anda harus mengubah penunjuk yang masuk karena hasilnya bukan NULLpenunjuk lagi. Ini adalah kasus di mana Anda memodifikasi penunjuk eksternal dari suatu fungsi, sehingga akan memiliki referensi ke penunjuk dalam tanda tangannya:

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

Ada contoh dalam pertanyaan ini .


8

Saya harus menggunakan kode seperti ini untuk menyediakan fungsi untuk mengalokasikan memori ke pointer yang dikirimkan dan mengembalikan ukurannya karena "objek" perusahaan saya menggunakan STL

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

Ini tidak bagus, tetapi penunjuk harus diteruskan dengan referensi (atau gunakan penunjuk ganda). Jika tidak, memori dialokasikan ke salinan lokal penunjuk jika diteruskan oleh nilai yang mengakibatkan kebocoran memori.


2

Salah satu contohnya adalah saat Anda menulis fungsi parser dan meneruskannya ke penunjuk sumber untuk dibaca, jika fungsi tersebut seharusnya mendorong penunjuk tersebut ke depan di belakang karakter terakhir yang telah dikenali dengan benar oleh parser. Menggunakan referensi ke sebuah pointer membuatnya jelas kemudian bahwa fungsi tersebut akan memindahkan pointer asli untuk memperbarui posisinya.

Secara umum, Anda menggunakan referensi ke pointer jika Anda ingin meneruskan pointer ke suatu fungsi dan membiarkannya memindahkan pointer asli ke posisi lain daripada hanya memindahkan salinannya tanpa mempengaruhi aslinya.


Mengapa suara negatif itu? Apakah ada yang salah dengan apa yang saya tulis? Mau menjelaskan? : P
BarbaraKwarc

0

Situasi lain ketika Anda mungkin memerlukan ini adalah jika Anda memiliki kumpulan pointer stl dan ingin mengubahnya menggunakan algoritma stl. Contoh for_each di 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();
  } 

};

Sebaliknya, jika Anda menggunakan tanda tangan changeObject (Object * item), Anda memiliki salinan pointer, bukan yang asli.


ini lebih merupakan "ganti objek" daripada "ubah objek" - ada perbedaan
serup
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.