Referanslar ve işaretçiler ne zaman kullanılır?


381

Referanslara karşı işaretçilerin sözdizimini ve genel anlambilimini anlıyorum, ancak bir API'da referansları veya işaretçileri kullanmanın ne zaman veya daha az uygun olduğuna nasıl karar vermeliyim?

Doğal olarak bazı durumların birine ya da diğerine operator++ihtiyacı vardır ( bir referans argümana ihtiyaç duyar), ancak genel olarak, sözdiziminin değişkenlerin yıkıcı bir şekilde iletildiği açık olduğu için işaretçiler (ve sabit işaretçiler) kullanmayı tercih ediyorum.

Örneğin, aşağıdaki kodda:

void add_one(int& n) { n += 1; }
void add_one(int* const n) { *n += 1; }
int main() {
  int a = 0;
  add_one(a); // Not clear that a may be modified
  add_one(&a); // 'a' is clearly being passed destructively
}

İşaretçi ile her zaman (daha fazla) neler olduğu açıktır, bu yüzden açıklığın büyük bir endişe olduğu API'lar ve benzerleri için işaretçiler referanslardan daha uygun değildir? Bu demek oluyor ki referanslar sadece gerektiğinde kullanılmalı operator++mı (örneğin )? Biriyle ya da diğeriyle ilgili herhangi bir performans kaygısı var mı?

DÜZENLE (AÇIK):

NULL değerlere izin vermenin ve ham dizilerle uğraşmanın yanı sıra, seçim kişisel tercihe bağlı görünüyor. Google'ın C ++ Stil Kılavuzu'na atıfta bulunan aşağıdaki cevabı kabul ettim .

NULL olmaması gereken işaretçi argümanlarını sterilize etmek için gereken ek çalışmalar nedeniyle (örn add_one(0). İşaretçi sürümünü çağıracak ve çalışma sırasında kırılacaktır), bir nesnenin mevcut olması gereken referansları kullanmak bir sürdürülebilirlik perspektifinden mantıklıdır, sözdizimsel netliğini kaybetmek.


4
Hangisinin ne zaman kullanılacağına karar vermişsiniz gibi görünüyor. Şahsen, değiştirdiğim ya da değiştirmediğim, üzerinde hareket ettiğim nesneyi iletmeyi tercih ederim. Bir işlev bir işaretçi alırsa, bu işaretçiler üzerinde hareket ettiğini, yani bunları bir dizide yineleyiciler olarak kullandığını söyler.
Benjamin Lindley

1
@Schnommus: Yeterince adil, çoğunlukla TextMate kullanıyorum. Yine de, anlamın bir bakışta açık olması tercih edilir.
connec

4
Değiştirilecek add_one(a);olanın belirsizliği ne aolacak? Kodda doğru diyor: bir tane ekleyin .
GManNickG

32
@connec: Google C ++ stil kılavuzu iyi bir C ++ stil kılavuzu olarak kabul edilmez. Google'ın eski C ++ kod tabanı ile çalışmak için bir stil kılavuzudur (yani, eşyaları için iyi). Buna dayalı bir cevabı kabul etmek kimseye yardım etmez. Sadece yorumlarınızı ve açıklamalarınızı okuduğunuzda, bu soruya önceden belirlenmiş bir görüşle geldiniz ve sadece görüşünüzü onaylamak için başka insanlar arıyorsunuz. Sonuç olarak, soruyu temel alıyor ve duymak / beklemek istediğinize cevap veriyorsunuz.
Martin York

1
Bu, yöntemi adlandırarak düzeltildi addOneTo(...). Yapmak istediğiniz şey bu değilse, sadece bildirime bakın.
stefan

Yanıtlar:


296

Mümkün olan her yerde referans, gerektiğinde işaretçiler kullanın

Yapamayana kadar işaretçilerden kaçının.

Bunun nedeni, işaretçilerin işleri takip etmeyi / okumayı zorlaştırır, daha az güvenli ve diğer yapılardan daha tehlikeli manipülasyonlar yapmasıdır.

Bu nedenle, temel kural yalnızca başka seçenek yoksa işaretçileri kullanmaktır.

Örneğin, işlev bazı durumlarda nullptr döndürebildiğinde ve nesnenin döneceği varsayıldığında, bir nesneye işaretçiyi döndürmek geçerli bir seçenektir. Bununla birlikte, daha iyi bir seçenek,boost::optional .

Başka bir örnek, belirli bellek manipülasyonları için ham belleği işaretçiler kullanmaktır. Bu, tüm kod tabanının tehlikeli kısımlarını sınırlamaya yardımcı olmak için kodun çok dar bölümlerinde gizlenmeli ve yerelleştirilmelidir.

Örneğinizde, bir işaretçiyi bağımsız değişken olarak kullanmanın bir anlamı yoktur, çünkü:

  1. eğer sağlarsan nullptr argüman olarak , tanımlanmamış-davranış-toprağa girersiniz;
  2. Referans öznitelik sürümü, 1 ile ilgili soruna izin vermez (kolay hileler olmadan).
  3. başvuru özniteliği sürümünün kullanıcı için anlaşılması daha kolaydır: null olabilecek bir şey değil, geçerli bir nesne sağlamanız gerekir.

İşlev davranışının belirli bir nesne ile veya nesne olmadan çalışması gerekiyorsa, öznitelik olarak bir işaretçi kullanmak nullptrbağımsız değişken olarak geçebileceğinizi ve işlev için iyi olduğunu gösterir. Bu, kullanıcı ve uygulama arasında bir tür sözleşme.


49
İşaretçilerin okunmasını zorlaştıracağından emin değilim? Bu oldukça basit bir kavramdır ve bir şeyin değiştirilmesi muhtemel olduğunda bunu netleştirir. Ne olduğunu gösteren hiçbir şey olmadığında okumak zor olduğunu söyleyeceğim bir şey, neden add_one(a)referans olarak belirtmek yerine sonucu döndürmüyoruz?
connec

46
@connec: add_one(a)Kafa karıştırıcıysa, bunun nedeni yanlış adlandırılmış olmasıdır. add_one(&a)aynı karışıklığa sahip olurdu, yalnızca şimdi işaretçiyi artırıyor olabilirsiniz, nesneyi değil. add_one_inplace(a)tüm karışıklıklardan kaçınırdı.
Nicol Bolas

20
Bir nokta, referanslar, işaretçiler kadar kolay gidebilen hafızaya atıfta bulunabilir. Bu yüzden mutlaka işaretçilerden daha güvenli değillerdir. Kalıcı ve başarılı referanslar, işaretçiler kadar tehlikeli olabilir.
Doug T.

6
@Klaim Ham işaretçiler demek istedim. C ++ 'ın işaretçileri olduğunu NULLve nullptrbir nedenden dolayı onlara sahip olduğunu kastediyorum . Ve bu "asla işaretçiler kullanmayın" ve / veya "asla NULL kullanmayın, her zaman kullanın boost::optional" vermek iyi düşünülmüş hatta gerçekçi bir tavsiye değildir . Bu sadece delilik. Beni yanlış anlamayın, ham işaretleyicilere C ++ 'da C'den daha az ihtiyaç duyulur, ancak yine de faydalıdırlar, bazı C ++ insanların iddia etmeyi istedikleri kadar "tehlikeli" değildirler ve bu da abartıdır: yalnızca bir işaretçi kullanmak ve return nullptr;eksik bir değeri belirtmek daha kolay olduğunda ... Neden tüm Boost'u içe aktarmalıyım?

5
@Klaim "NULL kullanmak kötü bir uygulamadır" - şimdi bu çok saçma. Ve ifkullanılmıyor while() { break; }mu? Bunun yerine kişi kullanılmalı, değil mi? Ayrıca, merak etmeyin, büyük kod tabanlarıyla gördüm ve çalıştım ve evet, dikkatsizseniz , sahiplik bir sorundur. Kurallara sadık kalırsanız, tutarlı bir şekilde kullanın ve kodunuzu yorumlayın ve dokümante edin. Ama sonuçta, sadece C kullanmalıyım çünkü C ++ için çok aptalım, değil mi?

62

Referanslar dahili olarak işaretçiler olarak uygulandığından performanslar tamamen aynıdır. Böylece bunun için endişelenmenize gerek yok.

Referansların ve işaretçilerin ne zaman kullanılacağına dair genel kabul görmüş bir sözleşme yoktur. Bazı durumlarda, referansları (örneğin kopya oluşturucu) iade etmeniz veya kabul etmeniz gerekir, ancak bunun dışında istediğiniz gibi yapabilirsiniz. Karşılaştığım oldukça yaygın bir kural, parametrenin var olan bir nesneye başvurması gerektiğinde başvurular ve NULL değeri tamam olduğunda işaretçiler kullanmaktır.

Bazı kodlama kuralları ( Google'ınki gibi ), her zaman işaretçiler veya const referansları kullanması gerektiğini reçete eder, çünkü referanslar biraz belirsiz sözdizimine sahiptir: referans davranışları vardır, ancak değer sözdizimi vardır.


10
Buna biraz eklemek için Google'ın stil kılavuzu, işlevlere giriş parametrelerinin sabit referanslar ve çıktıların işaretçiler olması gerektiğini söylüyor. Bunu beğendim çünkü bir girdi ve çıktı ne bir fonksiyon imzasını okuduğunuzda çok netleşiyor.
Dan

44
@ Dan: Google stil kılavuzu, Google'ın eski kodu içindir ve modern kodlama için kullanılmamalıdır. Aslında, yeni bir proje için oldukça kötü bir kodlama stili.
GManNickG

13
@connec: Bu şekilde söyleyeyim: null son derece geçerli bir işaretçi değeri. Her yerde bir işaretçi var, ona boş değer verebilirim. Sizin ikinci versiyonunu ergo add_oneedilir kırık : add_one(0); // passing a perfectly valid pointer value, güm. Boş olup olmadığını kontrol etmeniz gerekir. Bazı insanlar cevaplayacaktır: "iyi, sadece fonksiyonumun null ile çalışmadığını belgeleyeceğim". Bu iyi, ama sonra sorunun amacını yenersiniz: null değerinin iyi olup olmadığını görmek için belgelere bakacaksanız, işlev bildirimini de görürsünüz .
GManNickG

8
Eğer bir referans olsaydı, durumun böyle olduğunu görürdünüz. Böyle bir imbik, noktayı kaçırır: Referanslar , mevcut bir nesneye atıfta bulunan ve muhtemelen boş olmayan bir dil düzeyinde uygulanırken , işaretçilerin böyle bir kısıtlaması yoktur. Bence dil düzeyinde uygulama, belge düzeyinde uygulamadan daha güçlü ve daha az hataya açıktır. Bazıları buna şöyle cevap vermeye çalışacaktır: "Bak, boş referans: int& i = *((int*)0);Bu geçerli bir imbik değil. Önceki koddaki sorun referansla değil, işaretçi kullanımı ile ilgilidir . Referanslar hiçbir zaman sıfır değildir.
GManNickG

12
Merhaba, yorumlarda dil avukatı eksikliği gördüm, bu yüzden düzeltmeme izin verin: referanslar genellikle işaretçiler tarafından uygulanır, ancak standart böyle bir şey söylemez. Başka bir mekanizma kullanan bir uygulama% 100 şikayet olacaktır.
Thomas Bonini

34

Gönderen C ++ SSS Lite -

Mümkün olduğunda referanslar ve gerektiğinde işaretçiler kullanın.

Referanslar genellikle "yeniden oturmaya" ihtiyacınız olmadığında işaretçilerden daha çok tercih edilir. Bu genellikle referansların bir sınıfın genel arayüzünde en yararlı olduğu anlamına gelir. Referanslar genellikle bir nesnenin derisinde ve iç kısımda işaretçiler üzerinde görünür.

Yukarıdaki istisna, bir işlevin parametresinin veya dönüş değerinin bir "sentinel" referansına (bir nesneye başvurmayan bir referansa) ihtiyaç duyduğu durumdur. Bu genellikle en iyi bir işaretçiyi döndürerek / alarak ve NULL işaretçisine bu özel önemi vererek yapılır (başvurular, her zaman bir eşlenmemiş NULL işaretçisi değil, diğer ad nesnelerini içermelidir).

Not: Eski C satırı programcıları bazen arayanın kodunda açık olmayan referans semantikleri sağladığından referansları beğenmezler. Bununla birlikte, bazı C ++ deneyimlerinden sonra, bunun bir sorumluluktan ziyade bir varlık olan bir bilgi gizleme biçimi olduğunu çabucak fark eder. Örneğin, programcılar makinenin dili yerine sorunun dilinde yazmalıdır.


1
Bir API kullanıyorsanız, ne yaptığını bilmeniz ve geçirilen parametrenin değiştirilip değiştirilmediğini bilmeniz gerektiğini iddia edebilirim ... dikkate alınması gereken bir şey, ancak kendimi C programcılarıyla ( kendimi küçük C deneyimim olsa da). Daha açık sözdiziminin makinelere olduğu kadar programcılara da fayda sağladığını da ekleyeceğim.
connec

1
@connec: C programcısının dilleri için doğru olduğundan emin olun. Ancak C ++ 'ya C gibi davranma hatası yapmayın. Bu tamamen farklı bir dildir. C ++ 'ı C olarak ele alırsanız, sonuç olarak C with class(C ++ olmayan) yazmayı haklı olarak yazabilirsiniz .
Martin York

15

Temel kuralım:

  • Giden veya içeri / dışarı parametreleri için işaretçiler kullanın. Böylece değerin değişeceği görülebilir. (Kullanmalısınız &)
  • NULL parametresi kabul edilebilir bir değerse işaretçiler kullanın. ( constGelen bir parametre olduğundan emin olun )
  • NULL olamaz ve ilkel bir tür ( const T&) değilse, gelen parametre için başvuru kullanın .
  • Yeni oluşturulan bir nesneyi döndürürken işaretçiler veya akıllı işaretçiler kullanın.
  • Referanslar yerine yapı veya sınıf üyeleri olarak işaretçiler veya akıllı işaretçiler kullanın.
  • Takma ad için referansları kullanın (ör. int &current = someArray[i] )

Hangisini kullanırsanız kullanın, işlevlerinizi ve parametrelerinin anlamlarını, açık değilse, belgelemeyi unutmayın.


14

Feragatname: referansların NULL veya "ribaund" (başka bir deyişle onların takma adı oldukları nesneyi değiştiremeyeceği) gerçeğinden başka, gerçekten bir zevk meselesine iner, bu yüzden demeyeceğim "bu daha iyi".

Bununla birlikte, koddaki referanslarla netliği kaybettiğini düşünmüyorum, postadaki son ifadenize katılmıyorum. Örneğinizde,

add_one(&a);

daha net olabilir

add_one(a);

çünkü büyük olasılıkla a'nın değerinin değişeceğini biliyorsunuz. Öte yandan, fonksiyonun imzası

void add_one(int* const n);

biraz açık değil: n tek bir tam sayı mı yoksa bir dizi mi olacak? Bazen yalnızca (kötü belgelendirilmiş) başlıklara ve aşağıdaki gibi imzalara erişebilirsiniz:

foo(int* const a, int b);

ilk bakışta yorumlamak kolay değildir.

Imho, referanslar (yeniden) tahsisi veya yeniden bağlama (daha önce açıklanan anlamda) gerekmediğinde işaretçiler kadar iyidir. Ayrıca, bir geliştirici yalnızca diziler için işaretçiler kullanıyorsa, işlev imzaları biraz daha az belirsizdir. Operatörlerin söz diziminin referanslarla çok daha okunabilir olduğu gerçeğinden bahsetmiyoruz.


Her iki çözümün de nerede netlik kazandığını ve kaybolduğunu açık bir şekilde gösterdiğiniz için teşekkür ederiz. Başlangıçta işaretçi kampındaydım, ama bu çok mantıklı.
Zach Beavon-Collin

12

Zaten cevap diğerleri gibi: Daima kullanım referanslar, değişken bir varlık sürece NULL/ nullptrolduğunu gerçekten geçerli bir durum.

John Carmack'in konuya bakış açısı benzer:

NULL işaretçiler C / C ++ 'da en azından kodumuzda en büyük sorundur. Tek bir değerin hem bayrak hem de adres olarak ikili kullanımı, inanılmaz sayıda ölümcül soruna neden olur. C ++ referansları mümkün olduğunda işaretçiler üzerine tercih edilmelidir; bir referans "gerçekten" sadece bir işaretçi olsa da, NULL olmamak için örtük bir sözleşmeye sahiptir. İşaretçiler referanslara dönüştürüldüğünde NULL kontroller yapın, daha sonra sorunu göz ardı edebilirsiniz.

http://www.altdevblogaday.com/2011/12/24/static-code-analysis/

Düzenle 2012-03-13

Kullanıcı Bret Kuhns haklı olarak şunları söylüyor:

C ++ 11 standardı tamamlandı. Çoğu kod referansları, shared_ptr ve unique_ptr bir kombinasyonu ile mükemmel para cezası gerektiğini belirtmek için zaman bu iş parçacığı olduğunu düşünüyorum.

Yeterince doğrudur, ancak ham işaretçileri akıllı işaretçilerle değiştirirken bile soru hala devam etmektedir.

Örneğin, her iki std::unique_ptrve std::shared_ptrvarsayılan yapıcı aracılığıyla "boş" işaretçiler olarak inşa edilebilir:

... yani boş olmadığını doğrulamaksızın kullanmanın bir çökme riski taşıdığı anlamına geliyor.

Ve sonra, "akıllı bir işaretçiyi fonksiyon parametresi olarak nasıl geçiririz?"

Jon 'ın cevabı soru için C ++ - boost :: shared_ptr başvurular geçen ve aşağıdaki yorumlar bir istiyorum net kesim gibi olmadığı kopya veya başvuru akıllı işaretçi geçirerek, o zaman bile olduğunu göstermektedir (I "kendim lehine referans tarafından ", ancak yanlış olabilir).


1
C ++ 11 standardı tamamlandı. Sanırım çoğu kod referansların bir kombinasyonu ile mükemmel para cezası gerektiğini belirtmek için bu konu zaman,shared_ptr , ve unique_ptr. Mülkiyet semantiği ve giriş / çıkış parametre kuralları bu üç parça ve sabit bir arada ele alınır. Eski kod ve çok optimize edilmiş algoritmalarla uğraşmak dışında, C ++ 'da ham işaretleyicilere neredeyse hiç gerek yoktur. Kullanıldıkları alanlar mümkün olduğunca kapsüllenmeli ve ham göstergeleri semantik olarak uygun "modern" eşdeğerine dönüştürmelidir.
Bret Kuhns

1
Çoğu zaman akıllı işaretçiler geçirilmemeli, ancak boşluk için test edilmeli ve daha sonra içerdikleri nesne referans olarak geçirilmelidir. Akıllı bir işaretçiyi gerçekten geçirmeniz gereken tek zaman, sahipliğini başka bir nesneyle aktarırken (unique_ptr) veya paylaşırken (shared_ptr).
Luke Worth

@ povman: Tamamen katılıyorum: Sahiplik arayüzün bir parçası değilse (ve değiştirilmek üzere değilse, olmamalıdır), o zaman bir parametre (veya dönüş değeri) olarak akıllı bir işaretçi geçirmemeliyiz. Sahiplik arayüzün bir parçası olduğunda şey biraz daha karmaşık hale gelir. Örneğin, Sutter / Meyers, unique_ptr parametresi olarak parametre olarak nasıl geçileceğini tartışır: copy (Sutter) veya r-value reference (Meyers)? Bir antipattern, bir işaretçinin etrafını küresel bir paylaşılan_ptr'e geçirmeye dayanır, bu işaretçinin geçersiz kılınması riski vardır (çözüm, akıllı işaretçiyi yığına kopyalamaktadır)
paercebal

7

Bu bir zevk meselesi değil. İşte bazı kesin kurallar.

İçinde bildirildiği kapsamda statik olarak bildirilen bir değişkene başvurmak istiyorsanız, bir C ++ başvurusu kullanın ve mükemmel bir şekilde güvenli olacaktır. Aynı durum, statik olarak bildirilmiş bir akıllı işaretçi için de geçerlidir. Parametreleri başvuru ile iletmek bu kullanıma bir örnektir.

Bir kapsamdan bildirildiği kapsamdan daha geniş bir şeye başvurmak istiyorsanız, mükemmel bir şekilde güvenli olması için referans olarak sayılan akıllı bir işaretçi kullanmalısınız.

Sözdizimsel rahatlık için bir koleksiyona referans veren bir öğeye başvurabilirsiniz, ancak güvenli değildir; öğe istendiği zaman silinebilir.

Bir koleksiyonun bir öğesine güvenli bir şekilde başvurmak için referans olarak sayılan akıllı bir işaretçi kullanmalısınız.


5

Herhangi bir performans farkı o kadar küçük olur ki, daha az net olan yaklaşımı kullanarak haklı olmaz.

Birincisi, referansların genellikle daha üstün olduğu yerlerde bahsedilmeyen bir durum constreferanslardır. Basit olmayan türler için, const referencebir geçici geçiş yapmak geçici oluşturmayı önler ve endişelendiğiniz karışıklığa neden olmaz (çünkü değer değiştirilmedi). Burada, bir kişiyi bir işaretçi geçmeye zorlamak endişe duyduğunuz karışıklığa neden olur, çünkü bir işleve alınan ve iletilen adresi görmek, değerin değiştiğini düşünmenizi sağlayabilir.

Her halükarda, temelde size katılıyorum. İşlevin yaptığı şeyin çok açık olmadığı durumlarda değerlerini değiştirmek için referans alan işlevlerden hoşlanmıyorum. Ben de bu durumda işaretçiler kullanmayı tercih ederim.

Karmaşık türde bir değer döndürmeniz gerektiğinde, başvuruları tercih etme eğilimindeyim. Örneğin:

bool GetFooArray(array &foo); // my preference
bool GetFooArray(array *foo); // alternative

Burada işlev adı, bir dizide bilgi aldığınızın anlaşılmasını sağlar. Yani karışıklık yok.

Referansların temel avantajları, her zaman geçerli bir değer içermeleri, işaretçilerden daha temiz olmaları ve herhangi bir ek sözdizimine gerek kalmadan polimorfizmi desteklemeleridir. Bu avantajlardan hiçbiri geçerli değilse, bir işaretçi yerine bir referans tercih etmek için hiçbir neden yoktur.


4

Kopyalanan wiki -

Bunun bir sonucu olarak, birçok uygulamada, bir referans yoluyla otomatik veya statik ömürlü bir değişken üzerinde çalışan, sözdizimsel olarak doğrudan erişmeye benzer olsa da, maliyetli gizli gizlilik işlemleri içerebilmektedir. Referanslar, C ++ 'ın sözdizimsel olarak tartışmalı bir özelliğidir, çünkü bir tanımlayıcının dolaylılık düzeyini gizlerler; yani, işaretçilerin genellikle sözdizimsel olarak öne çıktığı C kodundan farklı olarak, büyük bir C ++ kodu bloğunda, erişilen nesnenin yerel veya global bir değişken olarak tanımlanması veya bir referans (örtülü işaretçi) olup olmadığı hemen belli olmayabilir başka bir konum, özellikle kod referansları ve işaretçileri karıştırıyorsa. Bu özellik, kötü yazılmış C ++ kodunun okunmasını ve hatalarının ayıklanmasını zorlaştırabilir (bkz. Takma adlandırma).

Buna% 100 katılıyorum ve bu yüzden sadece referans yapmak için çok iyi bir nedeniniz olduğunda bir referans kullanmanız gerektiğine inanıyorum.


Ben de büyük ölçüde katılıyorum, ancak NULL işaretçiler karşı yerleşik koruma kaybının tamamen sözdizimsel endişeler için biraz fazla maliyetli olduğu görüşüne geliyorum, özellikle - daha açık olmasına rağmen - işaretçi sözdizimi oldukça çirkin neyse.
connec

Durumun da önemli bir faktör olacağını düşünüyorum. Mevcut kod tabanı ağırlıklı olarak işaretçiler kullandığında referansları kullanmaya çalışırken kötü bir fikir olacağını düşünüyorum. Onların referans olmasını bekliyorsanız o zaman onların çok örtük olması belki daha az önemlidir ..
user606723

3

Unutmamanız gereken noktalar:

  1. İşaretçiler olabilir NULL, referanslar olamaz NULL.

  2. Referansların kullanımı daha kolaydır, constdeğeri değiştirmek istemediğimizde referans için kullanılabilir ve sadece bir fonksiyonda referansa ihtiyaç duyarız.

  3. Pointer bir ile kullanıldığında *referanslar bir birlikte kullanıldığında ise &.

  4. İşaretçi aritmetik işlemi gerektiğinde işaretçiler kullanın.

  5. Bir boşluk türüne işaretçileriniz olabilir int a=5; void *p = &a;ancak bir boşluk türüne referansınız olamaz.

Pointer Vs Referansı

void fun(int *a)
{
    cout<<a<<'\n'; // address of a = 0x7fff79f83eac
    cout<<*a<<'\n'; // value at a = 5
    cout<<a+1<<'\n'; // address of a increment by 4 bytes(int) = 0x7fff79f83eb0
    cout<<*(a+1)<<'\n'; // value here is by default = 0
}
void fun(int &a)
{
    cout<<a<<'\n'; // reference of original a passed a = 5
}
int a=5;
fun(&a);
fun(a);

Ne kullanacağına karar

İşaretçi : Dizi, bağlantı listesi, ağaç uygulamaları ve işaretçi aritmetiği için.

Referans : Fonksiyon parametrelerinde ve dönüş tiplerinde.


2

" Mümkün olan yerlerde referansları kullan " kuralı ile ilgili bir sorun vardır ve daha fazla kullanım için referans tutmak istiyorsanız ortaya çıkar. Bunu örnekle açıklamak için aşağıdaki sınıflara sahip olduğunuzu düşünün.

class SimCard
{
    public:
        explicit SimCard(int id):
            m_id(id)
        {
        }

        int getId() const
        {
            return m_id;
        }

    private:
        int m_id;
};

class RefPhone
{
    public:
        explicit RefPhone(const SimCard & card):
            m_card(card)
        {
        }

        int getSimId()
        {
            return m_card.getId();
        }

    private:
        const SimCard & m_card;
};

İlk başta RefPhone(const SimCard & card)yapıcıdaki parametrenin bir referans tarafından geçirilmesi iyi bir fikir gibi görünebilir , çünkü yapıcıya yanlış / null işaretçiler geçirilmesini önler. Bir şekilde yığına değişkenlerin tahsisini ve RAII'den faydalanmayı teşvik eder.

PtrPhone nullPhone(0);  //this will not happen that easily
SimCard * cardPtr = new SimCard(666);  //evil pointer
delete cardPtr;  //muahaha
PtrPhone uninitPhone(cardPtr);  //this will not happen that easily

Ama sonra geçiciler mutlu dünyanızı yok etmeye gelir.

RefPhone tempPhone(SimCard(666));   //evil temporary
//function referring to destroyed object
tempPhone.getSimId();    //this can happen

Referanslara körü körüne bağlı kalırsanız, temelde aynı etkiye sahip olan yok edilen nesnelere referansları saklamak için geçersiz işaretçiler geçirme olasılığınızı değiştirirsiniz.

edit: "Mümkün olan her yerde referans kullanın, gereken her yerde işaretçiler kullanın. Yapamazsınız kadar işaretçiler kaçının." kuralına sadık olduğunu unutmayın. en çok oylanan ve kabul edilen cevaptan (diğer cevaplar da bunu önerir). Açık olsa da, örnek, bu tür referansların kötü olduğunu göstermek değildir. Ancak, işaretçiler gibi yanlış kullanılabilirler ve koda kendi tehditlerini getirebilirler.


İşaretçiler ve referanslar arasında aşağıdaki farklar vardır.

  1. Geçiş değişkenleri söz konusu olduğunda, referansla geçiş, değere göre geçişe benzer, ancak işaretçi semantiğine sahiptir (işaretçi gibi çalışır).
  2. Referans doğrudan 0 (null) olarak başlatılamaz.
  3. Referans (başvuru, referans alınmayan nesne) değiştirilemez ("* const" işaretçisine eşdeğer).
  4. const başvuru geçici parametreyi kabul edebilir.
  5. Yerel sabit referanslar geçici nesnelerin ömrünü uzatır

Bunları göz önünde bulundurarak mevcut kurallarım aşağıdaki gibidir.

  • Bir işlev kapsamı içinde yerel olarak kullanılacak parametreler için referanslar kullanın.
  • 0 (null) kabul edilebilir parametre değeri olduğunda veya daha fazla kullanım için parametreyi saklamanız gerektiğinde işaretçiler kullanın. 0 (null) kabul edilebilirse, parametreye "_n" soneki ekliyorum, korumalı işaretçiyi (Qt'daki QPointer gibi) kullanın veya belgeyi belgeleyin. Akıllı işaretçiler de kullanabilirsiniz. Paylaşılan işaretçilerle normal işaretçilerden çok daha dikkatli olmalısınız (aksi takdirde tasarım belleği sızıntıları ve sorumluluk karmaşası ile sonuçlanabilirsiniz).

3
Örneğinizle ilgili sorun, referansların güvenli olmaması değil, özel üyelerinizi canlı tutmak için nesne örneğinizin kapsamı dışında bir şeye güveniyor olmanızdır. const SimCard & m_card;sadece kötü yazılmış bir koddur.
plamenko

@plamenko Korkarım örneğin amacını anlamıyorsunuz. const SimCard & m_cardDoğru olup olmadığı bağlama bağlıdır. Bu gönderideki mesaj, referansların güvensiz olmadığı anlamına gelmez (eğer biri zor çalışırsa olabilir). Mesaj, "mümkünse referansları kullan" mantrasına körü körüne bağlı kalmamanızdır. Örnek, "mümkün olduğunda referansları kullan" doktrininin agresif kullanımının bir sonucudur. Bu açık olmalı.
doc

Cevabınız beni rahatsız eden iki şey var çünkü bence konu hakkında daha fazla bilgi edinmeye çalışan birini yanıltabilir. 1. Yazı tek yönlüdür ve referansların kötü olduğu izlenimini almak kolaydır. Referansların nasıl kullanılamayacağına dair yalnızca tek bir örnek sağladınız. 2. Örneğinizde neyin yanlış olduğunu net olarak bilmiyordunuz. Evet, geçici muhrip olacak, ama yanlış olan bu çizgi değildi, sınıfın uygulanmasıdır.
plamenko

Neredeyse hiç üye olmamalısınız const SimCard & m_card. Geçicilerle verimli olmak istiyorsanız explicit RefPhone(const SimCard&& card)yapıcı ekleyin .
plamenko

@plamenko Eğer bazı temel anlayışla okuyamazsanız, o zaman benim yazı tarafından yanıltıcı olmaktan daha büyük bir probleminiz var. Nasıl daha net olabileceğimi bilmiyorum. İlk cümleye bak. "Mümkün olduğunda referansları kullan" mantra ile ilgili bir sorun var! Yayınımda nerede referansların kötü olduğunu belirten bir ifade buldunuz? Yazımın sonunda referansları nerede kullanacağınızı yazdınız, peki bu sonuçlarla nasıl geldiniz? Bu soruya doğrudan bir cevap değil mi?
doc

1

Aşağıda bazı yönergeler verilmiştir.

Bir işlev, değiştirilen verileri değiştirmeden kullanır:

  1. Veri nesnesi, yerleşik veri türü veya küçük bir yapı gibi küçükse, değere göre iletin.

  2. Veri nesnesi bir dizi ise, bir işaretçi kullanın çünkü bu sizin tek seçeneğinizdir. İşaretçiyi sabitlemek için bir işaretçi yapın.

  3. Veri nesnesi iyi boyutlu bir yapı ise, program verimliliğini artırmak için bir const işaretçisi veya bir const referansı kullanın. İşaretçiyi veya başvuru sabitini yapın.

  4. Veri nesnesi bir sınıf nesnesiyse, bir const referansı kullanın.

Bir işlev çağıran işlevdeki verileri değiştirir:

1.Veri nesnesi yerleşik bir veri türüyse, bir işaretçi kullanın. X'in bir int olduğu fixit (& x) gibi kodu tespit ederseniz, bu işlevin x'i değiştirmek istediği oldukça açıktır.

2.Veri nesnesi bir dizi ise, tek seçiminizi kullanın: bir işaretçi.

3.Veri nesnesi bir yapı ise, bir referans veya işaretçi kullanın.

Veri nesnesi bir sınıf nesnesiyse, bir başvuru kullanın.

Tabii ki, bunlar sadece yönergelerdir ve farklı seçimler yapmak için nedenler olabilir. Örneğin cin, cin >> & n yerine cin >> n'yi kullanabilmeniz için temel türler için referanslar kullanır.


0

Referanslar daha temiz ve kullanımı daha kolaydır ve bilgileri gizlemek için daha iyi bir iş çıkarırlar. Ancak, referanslar yeniden atanamaz. Önce bir nesneyi sonra başka bir nesneyi işaret etmeniz gerekiyorsa, bir işaretçi kullanmalısınız. Başvurular boş olamaz, bu nedenle söz konusu nesnenin boş olması ihtimali varsa, başvuru kullanmamalısınız. Bir işaretçi kullanmalısınız. Nesne manipülasyonunu kendi başınıza işlemek istiyorsanız, örneğin Yığın üzerindeki Yığın üzerindeki bir nesne için bellek alanı ayırmak istiyorsanız İşaretçi'yi kullanmalısınız

int *pInt = new int; // allocates *pInt on the Heap

0

Düzgün yazılmış örnek şöyle görünmelidir

void add_one(int& n) { n += 1; }
void add_one(int* const n)
{
  if (n)
    *n += 1;
}

Bu yüzden mümkünse referanslar tercih edilir ...


-1

Sadece kuruşumu sokuyorum. Bir test yaptım. O hapşırma biri. Ben sadece g ++ referansları ile karşılaştırıldığında işaretçiler kullanarak aynı mini programın montaj dosyalarını oluşturmak izin. Çıktıya bakarken bunlar tamamen aynıdır. Sembol ismi dışında. Performansa bakıldığında (basit bir örnekte) sorun yoktur.

Şimdi işaretçiler vs referanslar konusunda. IMHO Bence netlik her şeyden önce duruyor. Örtülü davranışları okuduğumda ayak parmaklarım kıvrılmaya başlar. Bir başvurunun NULL olamamasının hoş örtülü davranış olduğunu kabul ediyorum.

NULL işaretçisinin kaydı silme işlemi sorun değil. Uygulamanızı kilitleyecek ve hata ayıklamak kolay olacak. Daha büyük bir sorun, geçersiz değerler içeren başlatılmamış işaretçilerdir. Bu büyük olasılıkla, açık bir köken olmaksızın tanımlanmamış davranışa neden olan bellek bozulmasına neden olacaktır.

Referansların işaretçilerden çok daha güvenli olduğunu düşünüyorum. Ve önceki bir ifadeye katılıyorum, arayüzün (açıkça belgelenmesi gerekir, sözleşmeye göre tasarıma bakın, Bertrand Meyer) bir fonksiyonun parametrelerinin sonucunu tanımlar. Şimdi tüm bunları göz önünde bulundurarak tercihlerim, mümkün olan her yerde / zamanda referansları kullanmaya gitmektedir.


-2

İşaretçiler için, bir şeye işaret etmeleri gerekir, bu nedenle işaretçiler bellek alanına mal olur.

Örneğin, bir tamsayı işaretçisi alan bir işlev, tamsayı değişkenini almayacaktır. Bu nedenle, önce işleve geçmek için bir işaretçi oluşturmanız gerekir.

Bir referans gelince, bellek maliyeti olmayacak. Bir tamsayı değişkeniniz var ve bunu bir başvuru değişkeni olarak iletebilirsiniz. Bu kadar. Bunun için özel olarak bir referans değişkeni oluşturmanız gerekmez.


4
Hayır! İşaretçi alan bir işlev, işaretçi değişkeninin tahsis edilmesini gerektirmez: geçici bir geçiş yapabilirsiniz &address. Bir başvurunun bir nesnenin üyesi olması durumunda kesinlikle belleğe mal olur ve ayrıca tüm mevcut derleyiciler başvuruları adres olarak uygular, böylece parametre geçirme veya kayıttan kaldırma açısından hiçbir şey kaydetmezsiniz.
underscore_d
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.