C ++ 'ta referans ile geçerek işaretçi üzerinden geçmenin faydaları var mı?


225

C ++ 'ta referans ile geçerek işaretçi üzerinden geçmenin faydaları nelerdir?

Son zamanlarda, referansla geçmek yerine işaretçilerle geçirme işlevi argümanlarını seçen birkaç örnek gördüm. Bunu yapmanın faydaları var mı?

Misal:

func(SPRITE *x);

çağrısıyla

func(&mySprite);

vs.

func(SPRITE &x);

çağrısıyla

func(mySprite);

newBir işaretçi ve bunun sonucunda ortaya çıkan sahiplik sorunlarını oluşturmayı unutmayın .
Martin York

Yanıtlar:


217

Bir işaretçi bir NULL parametresi alabilir, bir referans parametresi alamaz. "Nesne yok" iletme olasılığınız varsa, başvuru yerine işaretçi kullanın.

Ayrıca, işaretçiyle iletme, çağrı sitesinde nesnenin değere göre mi yoksa başvuru ile mi geçtiğini açıkça görmenizi sağlar:

// Is mySprite passed by value or by reference?  You can't tell 
// without looking at the definition of func()
func(mySprite);

// func2 passes "by pointer" - no need to look up function definition
func2(&mySprite);

18
Eksik cevap. İşaretçiler kullanmak, geçici / yükseltilmiş nesnelerin kullanımına veya sivri uçlu nesnenin yığın benzeri nesneler olarak kullanılmasına izin vermez. Ve çoğu zaman bir NULL değerinin yasaklanması gerektiğinde argümanın NULL olabileceğini düşündürecektir. Tam bir cevap için litb'nin cevabını okuyun.
paercebal

İkinci işlev çağrısı ek açıklanırdı func2 passes by reference. Kod düzeyi perspektifte bir işaretçi geçirerek uygulanan yüksek düzeyli bir perspektiften "referansla" geçtiği anlamına geldiğinizi takdir etsem de, bu çok kafa karıştırıcıydı (bkz. Stackoverflow.com/questions/13382356/… ).
Yörüngedeki Hafiflik Yarışları

Bunu sadece satın almıyorum. Evet, bir işaretçi geçirirsiniz, bu nedenle bir çıkış parametresi olmalıdır, çünkü işaret edilen şey sabit olamaz?
deworde

C'de bu geçen referansımız yok mu? Ben kod bloğu (mingw) son sürümünü kullanıyorum ve bir C projesi seçerken. Hala referans ile geçerek (func (int & a)) çalışır. Yoksa C99 veya C11'den itibaren mi mevcut?
Jon Wheelock

1
@JonWheelock: Hayır, C'nin hiç doğrudan referansı yok. func(int& a)standardın hiçbir sürümünde geçerli C değildir. Muhtemelen yanlışlıkla C ++ olarak dosyalarınızı derliyorsunuz.
Adam Rosenfield

245

İşaretçi ile geçme

  • Arayanın adresi alması gerekir -> şeffaf değil
  • Ortalama olarak 0 değeri sağlanabilir nothing. Bu isteğe bağlı bağımsız değişkenler sağlamak için kullanılabilir.

Referans ile geç

  • Arayan nesneyi geçer -> şeffaf. İşaretçi türleri için aşırı yükleme mümkün olmadığından (işaretçiler yerleşik türlerdir) operatör aşırı yüklenmesi için kullanılmalıdır. Bu yüzden string s = &str1 + &str2;işaretçiler kullanarak yapamazsınız .
  • 0 değer mümkün değil -> Aranan fonksiyonun bunları kontrol etmesi gerekmez
  • Const'a yapılan başvurular ayrıca geçici geçişleri de kabul eder: void f(const T& t); ... f(T(a, b, c)); geçici bir adresin adresini alamadığınız için işaretçiler bu şekilde kullanılamaz.
  • Son fakat en az değil, referansların kullanımı daha kolaydır -> hatalar için daha az şans.

7
İşaretçi ile geçmek de 'Sahiplik aktarılıyor mu değil mi?' soru. Referanslarda durum böyle değildir.
Frerich Raabe

43
Ben "hata için daha az şans" ile katılmıyorum. Çağrı alanını incelerken okuyucu "foo (& s)" ifadesini görürken, s'nin değiştirilebileceği hemen anlaşılır. "Foo (s)" ifadesini okuduğunuzda, değiştirilip değiştirilemeyeceği açık değildir. Bu önemli bir hata kaynağıdır. Belki belirli bir böcek sınıfının şansı daha azdır, ancak genel olarak, referansla geçmek büyük bir hata kaynağıdır.
William Pursell

25
Ne demek "şeffaf"?
Gbert90

2
@ Gbert90, bir çağrı sitesinde foo (& a) görürseniz, foo () öğesinin işaretçi türünü aldığını bilirsiniz. Foo (a) öğesini görürseniz, bunun bir referans alıp almadığını bilmiyorsunuzdur.
Michael J. Davenport

3
@ MichaelJ.Davenport - açıklamanızda, "arayanın bir işaretçi geçtiği aşikar, ancak arayanın bir referansı geçtiği aşikar" satırları boyunca "şeffaf" ı öneriyorsunuz. Johannes'in gönderisinde, "İşaretçi ile geçme - Arayan adresi almalı -> saydam değil" ve "Referansla geç - Arayan sadece nesneyi geçirir -> saydam" - ki bu söylediklerinizin tam tersi . Bence Gbert90'ın "Şeffaf" ile ne demek istiyorsun? Sorusu hala geçerli.
Happy Green Kid Naps

66

"Cplusplus.com" dan bir makalenin muhakemesini seviyorum:

  1. İşlev parametreyi değiştirmek istemediğinde ve değerin kopyalanması kolay olduğunda (ints, double, double, char, bool vb.) Basit tipler olduğunda değere göre geçin. Std :: string, std :: vector ve diğer tüm STL kaplar basit tip DEĞİLDİR.)

  2. Değer kopyalamak pahalı olduğunda const pointer ile geçin VE fonksiyon, AND NULL'a gösterilen değeri değiştirmek istemiyorsa, işlevin işlediği geçerli, beklenen bir değerdir.

  3. Değerin kopyalanması pahalı olduğunda sabit olmayan gösterici ile geçin VE işlev, AND NULL'a gösterilen değeri değiştirmek istiyor ve işlevin işlediği geçerli, beklenen bir değerdir.

  4. Değerin kopyalanması pahalı olduğunda const referansı ile geçin VE işlev, bir işaretçi kullanılmışsa AND NULL'a atıf yapılan değeri değiştirmek istemezse geçerli bir değer olmaz.

  5. Değerin kopyalanması pahalı olduğunda devamı olmayan referansla geçin VE işlev, bir işaretçi kullanılmışsa, AND NULL değerine yönlendirilen değeri değiştirmek isterse geçerli bir değer olmaz.

  6. Şablon işlevleri yazarken, bu tartışmanın kapsamının ötesinde düşünülmesi gereken birkaç tutarsızlık olduğu için kesin bir cevap yoktur, ancak çoğu şablon işlevinin parametrelerini değer veya (const) referansına göre aldığını söylemeye yeter ancak yineleyici sözdizimi işaretçilerinkine benzediğinden (yıldız işaretinden "dereference" a), yineleyicileri bağımsız değişken olarak bekleyen herhangi bir şablon işlevi de varsayılan olarak işaretçileri de kabul eder (ve NULL yineleyici kavramının farklı bir sözdizimi olması nedeniyle NULL'u denetlemez ).

http://www.cplusplus.com/articles/z6vU7k9E/

Ne almak bir işaretçi veya başvuru parametresi kullanmayı seçmek arasındaki en büyük fark NULL kabul edilebilir bir değer olmasıdır. Bu kadar.

Değerin giriş, çıkış, değiştirilebilir vb. Olup olmadığı, sonuçta işlevin belgelerinde / yorumlarında olmalıdır.


Evet, benim için NULL ile ilgili terimler buradaki ana endişeler. Alıntı için
teşekkürler

62

Allen Holub'un "Ayakta Kendini Vurmak için Yeterli Halat" aşağıdaki 2 kuralı listeler:

120. Reference arguments should always be `const`
121. Never use references as outputs, use pointers

Referansların C ++ 'a eklenmesinin birkaç nedenini listeler:

  • kopya kurucuları tanımlamak için gereklidirler
  • operatör aşırı yüklemeleri için gereklidir
  • const referanslar, bir kopyadan kaçınırken değer-değeri anlambilimine sahip olmanızı sağlar

Ana noktası, referansların 'çıkış' parametreleri olarak kullanılmaması gerektiğidir, çünkü çağrı sitesinde parametrenin referans mı yoksa değer parametresi mi olduğuna dair bir gösterge yoktur. Yani kuralı sadece constreferansları argüman olarak kullanmaktır .

Şahsen, bir parametre bir çıkış parametresi olduğunda ya da olmadığında daha açık hale getirdiğinden, bu iyi bir kural olduğunu düşünüyorum. Bununla birlikte, genel olarak buna katılıyorum, ancak çıkış parametrelerini referans olarak (onlar gibi son derece geliştiriciler) tartışıyorlarsa, kendimi ekibimdeki başkalarının görüşleri tarafından sallanmasına izin veriyorum.


8
Bu argümandaki duruşum, işlev adı, belgeleri kontrol etmeden, parametrenin değiştirileceğini tamamen açık hale getirirse, const olmayan bir referansın OK olduğudur. Şahsen ben "getDetails (DetailStruct & sonuç)" izin verirdim. Oradaki bir işaretçi NULL girdisinin çirkin olasılığını artırır.
Steve Jessop

3
Bu yanıltıcı. Bazıları referanslardan hoşlanmasa bile, dilin önemli bir parçasıdır ve bu şekilde kullanılmalıdır. Bu akıl yürütme tarzı, herhangi bir türü saklamak için her zaman geçersiz * kapları kullanabileceğiniz şablonlar kullanma demeye benzer. Cevabı litb oku.
David Rodríguez - dribeas

4
Bunun nasıl yanıltıcı olduğunu görmüyorum - referansların gerekli olduğu zamanlar var ve mümkün olan en iyi uygulamaların bunları kullanmamayı önerebileceği zamanlar var. Aynı şey dilin herhangi bir özelliği için de söylenebilir - miras, üye olmayan arkadaşlar, operatör aşırı yüklenmesi, MI, vb ...
Michael Burr

Bu arada, litb'nin cevabının çok iyi olduğunu ve kesinlikle bundan daha kapsamlı olduğunu kabul ediyorum - referansları çıkış parametreleri olarak kullanmaktan kaçınmak için bir mantığı tartışmaya odaklanmayı seçtim.
Michael Burr

1
Bu kural google c kullanılır ++ stil rehberi: google-styleguide.googlecode.com/svn/trunk/...
Anton Daneyko

9

Önceki yazılara ilişkin açıklamalar:


Referanslar, boş gösterici alma garantisi DEĞİLDİR . (Sık sık onlara böyle davranmamıza rağmen.)

Korkunç kötü kod olsa da, odunluk kötü kodun arkasına çıkarırken , aşağıdakiler derlenecek ve çalışacaktır: (En azından derleyicimin altında.)

bool test( int & a)
{
  return (&a) == (int *) NULL;
}

int
main()
{
  int * i = (int *)NULL;
  cout << ( test(*i) ) << endl;
};

Referanslarla ilgili asıl sorun bundan böyle yapıcıya tahsis eden, yıkıcıya yerleşen ve bir kopya oluşturucu veya operatör = () sağlayamayan diğer programcılar ile ilgilidir .

Birden foo (BAR bar) ve foo (BAR & bar) arasında bir fark dünyası var . (Otomatik bitsel kopyalama işlemi çağrılır. Yıkıcıdaki ayırma iki kez çağrılır.)

Neyse ki modern derleyiciler aynı işaretçinin bu çift anlaşma yerini alacaktır. 15 yıl önce, yapmadılar. (Gcc / g ++ altında , eski yolları yeniden ziyaret etmek için setenv MALLOC_CHECK_ 0 kullanın.) Sonuç olarak, DEC UNIX altında, aynı bellekte iki farklı nesneye tahsis edilir. Orada hata ayıklama eğlenceli bir sürü ...


Daha pratik olarak:

  • Referanslar, başka bir yerde saklanan verileri değiştirdiğinizi gizler.
  • Bir Referansı bir Kopyalanan nesneyle karıştırmak kolaydır.
  • İşaretçiler bunu açıkça ortaya koyuyor!

16
bu işlevin veya referansların sorunu değildir. dil kurallarını çiğniyorsunuz. bir boş gösterici tek başına silme zaten tanımsız bir davranıştır. "Referanslar, boş olmayan bir işaretçi elde etmenin garantisi DEĞİLDİR.": Standardın kendisi olduğunu söylüyor. diğer yollar tanımlanmamış davranışı oluşturur.
Johannes Schaub - litb

1
Litb ile aynı fikirdeyim. Doğru olsa da, bize gösterdiğiniz kod her şeyden daha fazla sabotajdır. Hem "referans" hem de "işaretçi" gösterimleri de dahil olmak üzere her şeyi sabote etmenin yolları vardır.
paercebal

1
Ben "odunluk kötü kodun arkasına çıkar" olduğunu söyledi mi! Aynı şekilde, i = new FOO'ya da sahip olabilirsiniz; i'yi sil; Test (* ı); Başka (ne yazık ki yaygın) sarkan bir işaretçi / referans oluşumu.
Mr.Ree

1
Aslında değil dereferencing problem NULL, daha ziyade KULLANARAK o indirgenmedikleri (null) nesne. Bu nedenle, işaretler ve referanslar arasında bir dil-uygulama perspektifinden gerçekten (sözdizimi dışında) bir fark yoktur. Farklı beklentileri olan kullanıcılar.
Mr.Ree

2
Döndürülen referansla ne yaparsanız yapın, söylediğiniz anda *iprogramınızın tanımlanmamış davranışı vardır. Örneğin, derleyici bu kodu görebilir ve "Tamam, bu kodun tüm kod yollarında tanımsız davranışa sahip olduğunu varsayabilir, bu nedenle tüm işleve erişilemez olması gerekir." Daha sonra, bu işleve yol açan tüm dalların alınmadığını varsayacaktır. Bu düzenli olarak yapılan bir optimizasyondur.
David Stone

5

Buradaki cevapların çoğu, bir işlev imzasında ham bir göstergeye sahip olma konusundaki doğal belirsizliği, ifade etme anlamında ele almamaktadır. Sorunlar şunlardır:

  • Arayan, işaretçinin tek bir nesneyi mi yoksa nesnelerin bir "dizisinin" başlangıcını mı işaret ettiğini bilmez.

  • Arayan, işaretçinin işaret ettiği belleğe "sahip olup olmadığını" bilmez. IE, fonksiyonun hafızayı boşaltması gerekip gerekmediği. ( foo(new int)- Bu bir bellek sızıntısı mı?).

  • Arayan nullptr, işleve güvenli bir şekilde aktarılıp aktarılamayacağını bilmiyor .

Bu sorunların tümü referanslarla çözülmüştür:

  • Referanslar her zaman tek bir nesneyi ifade eder.

  • Referanslar asla bahsettikleri belleğe sahip değildir, sadece belleğe bir bakıştır.

  • Referanslar boş olamaz.

Bu referansları genel kullanım için çok daha iyi bir aday yapar. Bununla birlikte, referanslar mükemmel değildir - dikkate alınması gereken birkaç önemli sorun vardır.

  • Açık bir dolaylama yok. Ham işaretçiyle ilgili bir sorun değil, çünkü& gerçekten bir işaretçiyi geçtiğimizi göstermek operatörü gerekiyor. Örneğin,int a = 5; foo(a); burada a'nın referans ile geçildiği ve değiştirilebileceği açık değildir.
  • Nullability. İşaretçilerin bu zayıflığı, aslında referanslarımızın geçersiz kılınmasını istediğimizde de bir güç olabilir. std::optional<T&>Geçerli olmadığını gören (iyi nedenlerle), işaretçiler bize istediğiniz bu boşluğu verir.

Görünüşe göre, açık dolaylı olarak null olabilecek bir referans istediğimizde, T* hak elde etmek ? Yanlış!

Soyutlamalar

Nullabilite için umutsuzluğumuzda, T*daha önce listelenen tüm eksikliklere ve anlamsal belirsizliklere ulaşabilir ve bunları göz ardı edebiliriz. Bunun yerine, C ++ 'ın en iyi yaptığı şeye ulaşmalıyız: bir soyutlama. Sadece bir işaretçiyi saran bir sınıf yazarsak, ifade edilebilme özelliğinin yanı sıra nullabilite ve açık dolaylılık da kazanırız.

template <typename T>
struct optional_ref {
  optional_ref() : ptr(nullptr) {}
  optional_ref(T* t) : ptr(t) {}
  optional_ref(std::nullptr_t) : ptr(nullptr) {}

  T& get() const {
    return *ptr;
  }

  explicit operator bool() const {
    return bool(ptr);
  }

private:
  T* ptr;
};

Bu, karşılaşabileceğim en basit arayüz, ancak işi etkili bir şekilde yapıyor. Referansı başlatmaya, bir değerin var olup olmadığını kontrol etmeye ve değere erişmeye izin verir. Bunu şu şekilde kullanabiliriz:

void foo(optional_ref<int> x) {
  if (x) {
    auto y = x.get();
    // use y here
  }
}

int x = 5;
foo(&x); // explicit indirection here
foo(nullptr); // nullability

Hedeflerimize ulaştık! Şimdi ham işaretçiye kıyasla avantajları görelim.

  • Arayüz, referansın sadece bir nesneye atıfta bulunması gerektiğini açıkça göstermektedir.
  • Açıkçası, kullanıcı tanımlı bir yıkıcıya ve hafızayı silme yöntemine sahip olmadığı için, atıfta bulunduğu belleğe sahip değildir.
  • nullptrİşlev yazarı açıkça bir istekte bulunduğundan, arayan kişinin iletilebileceğini biliroptional_ref

Arayüzü buradan daha karmaşık hale getirebiliriz, örneğin eşitlik operatörleri, bir monadik get_orve maparayüz, değeri alan veya bir istisna atan bir yöntem,constexpr destek. Bu sizin tarafınızdan yapılabilir.

Sonuç olarak, ham işaretçiler kullanmak yerine, bu işaretçilerin kodunuzda gerçekte ne anlama geldiğinin nedeni ve standart bir kütüphane soyutlamasından faydalanın veya kendinizinkini yazın. Bu, kodunuzu önemli ölçüde geliştirecektir.


3

Pek sayılmaz. Dahili olarak referans ile geçiş, esasen referans alınan nesnenin adresini geçirerek gerçekleştirilir. Yani, gerçekten bir işaretçi geçerek elde edilecek herhangi bir verimlilik kazancı yoktur.

Bununla birlikte, referansla geçmenin bir yararı vardır. Herhangi bir nesne / türün geçtiği bir örneğe sahip olmanız garanti edilir. Bir işaretçiyi iletirseniz, NULL işaretçisi alma riskini alırsınız. Tek tek başvuru yoluyla, örtük bir NULL denetimini işlevinizin arayanına bir düzey yukarı itersiniz.


1
Bu hem bir avantaj hem de dezavantaj. Birçok API, NULL işaretçileri kullanarak yararlı bir şey ifade eder (yani, NULL timespec sonsuza kadar beklerken değer, bu kadar beklemek anlamına gelir).
Greg Rogers

1
@Brian: Ben sirke-toplama olmak istemem ama: Ne olur değil bir söylemek garantili bir başvuru alırken bir örneğini almak için. Bir işlevin arayanı, arayanın bilemeyeceği bir sarkan göstergenin referansını kaldırırsa, sarkan referanslar hala mümkündür.
foraidt

bazen herhangi bir depolama alanı almaları gerekmediği ve kendilerine atanmış adresleri olmadığı için referansları kullanarak performans da elde edebilirsiniz. dolaylı yayın gerektirmez.
Johannes Schaub - litb

Sarkan referanslar içeren programlar geçerli C ++ değildir. Bu nedenle, evet, kod olabilir tüm referanslar geçerli olduğunu varsayalım.
Konrad Rudolph

2
Kesinlikle boş bir işaretçi dereference ve derleyici söyleyemeyiz ... derleyici "geçersiz C ++" söyleyemezse, gerçekten geçersiz mi?
rmeador
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.