Bir İşaretçiyi C ++ 'da Başvuruyla Geçmenin Nedeni


101

Hangi koşullarda bu tür bir kodu c ++ 'da kullanmak istersiniz?

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

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

bir işaretçi döndürmeniz gerekiyorsa - daha iyi bir dönüş değeri kullanın
littleadv

6
Nedenini açıklayabilir misin? Bu övgü pek yardımcı olmadı. Sorum oldukça yasal. Bu şu anda üretim kodunda kullanılıyor. Nedenini tam olarak anlamıyorum.
Matthew Hoggan

1
David bunu oldukça güzel özetliyor. İşaretçinin kendisi değiştiriliyor.
chris

2
İşlevde "Yeni" operatörünü çağıracaksam bu yolu geçiyorum.
NDEthos

Yanıtlar:


148

İmlecin işaret ettiği nesne yerine işaretçiyi değiştirmeye ihtiyacınız varsa, bir işaretçiyi referans olarak geçirmek isteyebilirsiniz.

Bu, neden çift işaretçilerin kullanıldığına benzer; bir işaretçiye başvuru kullanmak, işaretçi kullanmaktan biraz daha güvenlidir.


14
Bir göstericinin işaretçisine benzer, ancak referansla geçiş semantiği için bir daha az dolaylılık düzeyi dışında mı?

Bazen bir işaretçi referans olarak da döndürmek istediğinizi eklemek isterim. Örneğin, dinamik olarak ayrılmış bir dizide veri tutan bir sınıfınız varsa, ancak istemciye bu verilere (sabit olmayan) erişim sağlamak istiyorsanız. Aynı zamanda, istemcinin hafızayı işaretçi aracılığıyla değiştirmesini istemezsiniz.

2
@William bunu detaylandırır mısınız? Kulağa ilginç geliyor. Bu, bu sınıfın kullanıcısının dahili veri arabelleğine bir işaretçi alabileceği ve onu okuyabileceği / yazabileceği anlamına mı geliyor, ancak bu arabelleği kullanarak bu arabelleği serbest deletebırakamıyor veya bellekteki başka bir yere yeniden bağlayamıyor mu? Yoksa yanlış mı anladım?
BarbaraKwarc

2
@BarbaraKwarc Elbette. Dinamik bir dizinin basit bir uygulamasına sahip olduğunuzu varsayalım. Doğal olarak []operatörü aşırı yükleyeceksiniz. Bunun olası (ve aslında doğal) bir imzası olabilir const T &operator[](size_t index) const. Ama sen de yapabilirdin T &operator[](size_t index). İkisine de aynı anda sahip olabilirsiniz. İkincisi, buna benzer şeyler yapmanıza izin verirdi myArray[jj] = 42. Aynı zamanda, verilerinize bir işaretçi sağlamazsınız, böylece arayan kişi hafızayı karıştıramaz (örneğin, yanlışlıkla silebilirsiniz).

Oh. Bir göstericiye bir referans döndürmekten bahsettiğinizi sanıyordum (tam olarak ifadeniz: "referansla bir işaretçiyi geri getirin", çünkü OP'nin sorduğu şey buydu: işaretçileri referanslarla geçirmek, sadece eski referanslar değil) arabelleğin kendisini değiştirmesine izin vermeden arabelleğe okuma / yazma erişimi vermek (örneğin, onu serbest bırakmak). Eski bir referansa geri dönmek farklı bir şey ve bunu biliyorum.
BarbaraKwarc

67

C ++ programcılarının% 50'si, bir silme işleminden sonra işaretçilerini boş olarak ayarlamak ister:

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

Referans olmadan, arayanı etkilemeden sadece işaretçinin yerel bir kopyasını değiştirmiş olursunuz.


19
İsmini beğendim; )
4pie 0

3
Cevabı bulduğum için kulağa aptalca davranacağım: Neden buna aptalca silme deniyor? Neyi kaçırıyorum?
Sammaron

1
@Sammaron Geçmişe bakarsanız, işlev şablonu çağrılırdı paranoid_delete, ancak Puppy olarak yeniden adlandırılırdı moronic_delete. Muhtemelen diğer% 50'ye aittir;) Her neyse, kısa cevap şudur: Null after ayarlarının deleteneredeyse hiçbir zaman yararlı olmadığı (çünkü yine de kapsam dışına çıkmaları gerekir) ve çoğu zaman "ücretsiz sonra kullan" hatalarının tespitini engeller.
fredoverflow

@fredoverflow Cevabınızı anlıyorum, ancak @Puppy deletegenel olarak aptalca olduğunu düşünüyor . Yavru köpek, biraz googling yaptım, silme işleminin neden tamamen işe yaramaz olduğunu anlayamıyorum (belki de ben de berbat bir Google çalışanıyım;)). Biraz daha açıklayabilir misin veya bir bağlantı verebilir misin?
Sammaron

@Sammaron, C ++ artık normal işaretçileri kapsülleyen ve kapsam dışına çıktıklarında sizin için otomatik olarak "sil" diyen "akıllı işaretçiler" e sahiptir. Akıllı işaretçiler, bu sorunu tamamen ortadan kaldırdıkları için C tarzı aptal işaretçilerden büyük ölçüde tercih edilir.
tjwrona1992

15

David'in cevabı doğru, ancak yine de biraz soyutsa, işte iki örnek:

  1. Bellek sorunlarını daha önce yakalamak için tüm serbest işaretçileri sıfırlamak isteyebilirsiniz. Yapacağınız C tarzı:

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

    C ++ 'da aynısını yapmak için şunları yapabilirsiniz:

    template<class T> void freeAndZero(T* &ptr)
    {
        delete ptr;
        ptr = 0;
    }
    
    int* ptr = new int;
    
    ...
    
    freeAndZero(ptr);
    
  2. Bağlantılı listelerle uğraşırken - genellikle bir sonraki düğüme işaretçiler olarak gösterilir:

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

    Bu durumda, boş listeye eklediğinizde, gelen işaretçiyi mutlaka değiştirmeniz gerekir çünkü sonuç NULLartık işaretçi değildir . Bu, bir işlevden harici bir işaretçiyi değiştirdiğiniz bir durumdur, böylece imzasında işaretçiye bir başvuru olabilir:

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

Bu soruda bir örnek var .


8

Geçilen bir işaretçiye bellek ayırmak ve boyutunu döndürmek için işlevler sağlamak için böyle bir kod kullanmak zorunda kaldım çünkü şirketim STL'yi kullanarak bana "nesne"

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

Bu hoş değil, ancak gösterici referansla geçmelidir (veya çift işaretçi kullanın). Değilse, değer tarafından geçirilirse, göstericinin yerel bir kopyasına bellek ayrılır ve bu da bellek sızıntısına neden olur.


2

Bir örnek, bir ayrıştırıcı işlevi yazıp okumak için bir kaynak işaretçisi ilettiğinizde, işlevin bu işaretçiyi ayrıştırıcı tarafından doğru şekilde tanınan son karakterin arkasına itmesi gerekiyorsa. Bir işaretçiye referans kullanmak, işlevin konumunu güncellemek için orijinal işaretçiyi hareket ettireceğini netleştirir.

Genel olarak, bir işleve bir işaretçiyi iletmek ve orijinal işaretçiyi orijinali etkilemeden bir kopyasını taşımak yerine başka bir konuma taşımak istiyorsanız, işaretçiler için referanslar kullanırsınız .


Neden olumsuz oy? Yazdıklarımda bir sorun mu var? Açıklamak ister misin? : P
BarbaraKwarc

0

Buna ihtiyaç duyabileceğiniz başka bir durum, stl işaretçiler koleksiyonunuz varsa ve bunları stl algoritması kullanarak değiştirmek istemenizdir. C ++ 98'de for_each örneği.

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();
  } 

};

Aksi takdirde, changeObject (Nesne * öğe) imzasını kullanırsanız, orijinal değil, işaretçinin kopyasına sahip olursunuz.


bu, bir "değişim nesnesi"
nden
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.