Üye verilerindeki işaretçileri veya referansları mı tercih etmeliyim?


138

Bu, soruyu açıklamak için basitleştirilmiş bir örnektir:

class A {};

class B
{
    B(A& a) : a(a) {}
    A& a;
};

class C
{
    C() : b(a) {} 
    A a;
    B b; 
};

B, C'nin bir kısmını güncellemekten sorumludur.Kodu tüy bırakmadan çalıştırdım ve referans üye hakkında konuştu: lif # 1725 . Bu, varsayılan kopya ve atamalara yeterince adil bakmaktan bahseder, ancak varsayılan kopya ve atama da işaretçilerle kötüdür, bu nedenle orada çok az avantaj vardır.

Her zaman referansları kullanmaya çalışıyorum çünkü çıplak işaretçiler bu işaretçiyi silmekle kimin sorumlu olduğu konusunda belirsiz bir şekilde ortaya çıkıyor. Nesneleri değere göre yerleştirmeyi tercih ederim, ancak bir işaretçiye ihtiyacım varsa, işaretçinin sahibi olan sınıfın üye verilerinde auto_ptr kullanın ve nesneyi başvuru olarak iletin.

Genellikle işaretçi boş veya değişebilir üye verileri yalnızca bir işaretçi kullanmak istiyorsunuz. Veri üyeleri için referanslar yerine işaretçileri tercih etmenin başka nedenleri var mı?

Bir referans başlatıldıktan sonra bir referans değiştirilmemesi gerektiğinden referans içeren bir nesnenin atanması gerektiğini söylemek doğru mudur?


"ancak varsayılan kopyalama ve atama işaretçilerle de kötü": Bu aynı değil: Sabit değilse işaretçi istediğiniz zaman değiştirilebilir. Bir referans normalde her zaman sabittir! (Üyenizi "A & const a;" olarak değiştirmeye çalışırsanız bunu görürsünüz. Derleyici (en azından GCC), "const" anahtar kelimesi olmasa bile referansın yine de const olduğu konusunda sizi uyarır.
mmmmmmmm

Bununla ilgili temel sorun, eğer biri Bb (A ()) yaparsa, sarkan bir referansla sonuçlandığınız için vidalanırsınız.
log0

Yanıtlar:


67

Referans üyelerinden kaçının, çünkü bir sınıfın uygulanmasını yapabileceklerini kısıtlarlar (sizin de bahsettiğiniz gibi, bir atama operatörünün uygulanmasını engelleme dahil) ve sınıfın sağlayabileceği faydaları sağlamazlar.

Örnek problemler:

  • referansı her kurucunun başlatma listesinde başlatmaya zorlanırsınız: bu başlatmayı başka bir işleve dahil etmenin bir yolu yoktur ( C ++ 0x'a kadar, yine de düzenleme: C ++ artık temsilci oluşturuculara sahiptir )
  • başvuru geri alınamaz veya boş olamaz. Bu bir avantaj olabilir, ancak kodun yeniden bağlanmaya izin vermek veya üyenin boş olması için değiştirilmesi gerekiyorsa, üyenin tüm kullanımlarının değişmesi gerekir
  • işaretçi üyelerinden farklı olarak, yeniden düzenleme gerektirebileceğinden referanslar kolayca akıllı işaretçiler veya yineleyicilerle değiştirilemez
  • Bir referans kullanıldığında değer türüne ( .operatör vb.) Benzer, ancak bir işaretçi gibi davranabilir (sarkabilir) - bu nedenle, örneğin Google Stil Kılavuzu bunu reddeder

66
Bahsettiğiniz her şey kaçınılması gereken iyi şeylerdir, bu nedenle referanslar buna yardımcı olursa - iyi ve kötü değiller. Başlatma listesi, verileri başlatmak için en iyi yerdir. Çoğu zaman atama operatörünü, gerekmediğiniz referanslarla gizlemeniz gerekir. "Geri tepme olamaz" - iyi, değişkenlerin yeniden kullanılması kötü bir fikirdir.
Mykola Golubyev

4
@Mykola: Sana katılıyorum. Üyelerin başlatma listelerinde başlatılmasını tercih ederim, null işaretçilerden kaçınırım ve bir değişkenin anlamını değiştirmesi kesinlikle iyi değildir. Görüşlerimizin farklı olduğu yerlerde, derleyicinin bunu benim için uygulamasına gerek yok. Sınıfları yazmak daha kolay yapmaz, bu alanda yakalayacağı hatalarla karşılaşmadım ve bazen işaretçi üyelerini (uygun olan yerlerde akıllı işaretçiler) kullanan koddan aldığım esnekliği takdir ediyorum.
James Hopkin

Sınıf yine de silmek için sorumlu olmayan bir işaretçi içeriyorsa atama gizlemek zorunda değilsiniz çünkü atama gizlemek zorunda değilsiniz sahte bir argüman olduğunu düşünüyorum - varsayılan yapacağız. Bunun en iyi yanıt olduğunu düşünüyorum çünkü üye verilerindeki referanslara göre işaretçilerin tercih edilmesi için iyi nedenler veriyor.
markh44

4
James, kodun yazılmasını kolaylaştırdığı için kodun okunmasını kolaylaştırıyor. Veri üyesi olarak referans alarak, kullanım noktasında boş olup olmayacağını asla merak etmenize gerek yoktur. Bu, daha az bağlam gerektiren bir koda bakabileceğiniz anlamına gelir.
Len Holgate

4
Ünite testini ve alay etmeyi çok daha zor, neredeyse kaçının kullanıldığına bağlı olarak imkansız hale getirir , 'grafik patlamasını etkiler' ile birlikte IMHO bunları asla kullanmamak için yeterli nedenleri zorlar.
Chris Huang-Leaver

156

Kendi kuralım:

  • Nesnenizin ömrünün diğer nesnelerin yaşamına bağlı olmasını istediğinizde bir referans üyesi kullanın : nesnenin başka bir sınıfın geçerli bir örneği olmadan hayatta olmasına izin vermediğini söylemenin açık bir yolu - hayır atama ve yapıcı aracılığıyla referansların başlatılması yükümlülüğü. Başka bir sınıfın üyesi olup olmadığı konusunda herhangi bir şey varsaymadan sınıfınızı tasarlamanın iyi bir yoludur. Sadece yaşamlarının doğrudan diğer örneklerle bağlantılı olduğunu varsayıyorsunuz. Daha sonra sınıf örneğinizi nasıl kullanacağınızı değiştirmenize olanak tanır (yenisiyle yerel bir örnek olarak, sınıf üyesi olarak, bir yöneticideki bellek havuzu tarafından oluşturulan vb.)
  • Diğer durumlarda işaretçi kullanma : Üyenin daha sonra değiştirilmesini istediğinizde, yalnızca işaretlenen örneği okuduğunuzdan emin olmak için bir işaretçi veya sabit işaretçi kullanın. Bu türün kopyalanabilir olması gerekiyorsa, yine de referans kullanamazsınız. Bazen üyeyi özel bir işlev çağrısından (örneğin init ()) sonra başlatmanız gerekir ve daha sonra bir işaretçi kullanmaktan başka seçeneğiniz yoktur. ANCAK: yanlış işaretçi durumunu hızlı bir şekilde tespit etmek için tüm üye fonksiyonunuzdaki ekleri kullanın!
  • Eğer nesne ömrü harici nesnenin ömrü bağımlı olmak istiyorum ve ayrıca tip copyable sonra kurucusundaki işaretçi üyelerini ancak referans değişkeni kullanmaya Buna ihtiyacım durumlarda yolu bu nesnenin ömrü bağlı olduğu inşaat belirten vardır That bağımsız değişkenin ömür boyu ANCAK uygulama hala kopyalanabilir olmak için işaretçiler kullanın. Bu üyeler yalnızca kopya ile değiştirildiği ve türünüzde varsayılan bir oluşturucu bulunmadığı sürece, türün her iki hedefi de yerine getirmesi gerekir.

1
Sizin ve Mykola'nın doğru cevaplar olduğunu ve işaretsiz (daha az işaretçi) programlamayı teşvik ettiğini düşünüyorum.
amit

37

Nesneler nadiren atama ve karşılaştırma gibi diğer şeylere izin vermelidir. 'Departman', 'Çalışan', 'Yönetmen' gibi nesnelerle bir iş modeli düşünürseniz, bir çalışanın diğerine tayin edileceği bir durumu hayal etmek zordur.

Bu nedenle, iş nesneleri için bire bir ve bire çok ilişkileri referanslar olarak değil, referanslar olarak tanımlamak çok iyidir.

Ve muhtemelen bir veya sıfır ilişkisini bir işaretçi olarak tanımlamakta sorun yoktur.

Yani hayır “atayamayız” o zaman faktör.
Birçok programcı sadece işaretçilerle birlikte kullanılır ve bu nedenle referansın kullanılmasını önlemek için herhangi bir argüman bulacaklardır.

Üye olarak işaretçi kullanmak, sizi veya ekibinizin üyesini, kullanmadan önce işaretçiyi "her ihtimale karşı" yorumuyla tekrar tekrar kontrol etmeye zorlar. Bir işaretçi sıfır olabilirse, işaretçi muhtemelen her türün kendi rolünü oynaması gerektiği için kötü bir tür bayrak olarak kullanılır.


2
+1: Yaşadığım gibi. Yalnızca bazı genel veri depolama sınıflarının atama ve kopyalama-c'tor'larına ihtiyacı vardır. Ve daha üst düzey iş nesnesi çerçeveleri için, "benzersiz alanları kopyalamayın" ve "başka bir üst öğeye ekleyin" vb. ve düşük düzeyli atamalara izin vermeyin.
mmmmmmmm

2
+1: Bazı anlaşma noktaları: Bir atama operatörünün tanımlanmasını önleyen referans üyeler önemli bir argüman değildir. Farklı bir şekilde doğru şekilde ifade ettiğinizde, tüm nesnelerin değer semantiği yoktur. Ayrıca 'p (p)' nin mantıklı bir sebep olmadan kodun her tarafına dağılmasını istemediğimizi de kabul ediyorum. Ancak buna doğru yaklaşım sınıf değişmezlerinden geçer: iyi tanımlanmış bir sınıf, üyelerin boş olup olmayacağı konusunda şüpheye yer bırakmayacaktır. Kodda herhangi bir işaretçi boş olabilir, ben iyi yorumlanmasını beklenir.
James Hopkin

@JamesHopkin İyi tasarlanmış sınıfların işaretçileri etkili bir şekilde kullanabileceğini kabul ediyorum. NULL'a karşı korumanın yanı sıra, referanslar referansınızın başlatıldığını da garanti eder (bir işaretçinin başlatılması gerekmez, bu yüzden başka bir şeye işaret edebilir). Referanslar ayrıca size sahiplik hakkında bilgi verir - nesnenin üyeye sahip olmadığı. Toplu üyeler için sürekli olarak başvurular kullanırsanız, işaretçi üyelerinin bileşik nesneler olduğuna çok yüksek bir güveniniz olur (ancak bileşik bir üyenin bir işaretçi ile temsil edildiği pek çok durum yoktur).
weberc2


6

Birkaç önemli durumda, atanabilirliğe ihtiyaç yoktur. Bunlar genellikle kapsamdan çıkmadan hesaplamayı kolaylaştıran hafif algoritma sarmalayıcılarıdır. Bu tür nesneler, her zaman geçerli bir referansa sahip olduklarından ve hiçbir zaman kopyalanmaları gerekmediğinden emin olabileceğinizden referans üyeleri için birincil adaylardır .

Bu gibi durumlarda, atama operatörünü (ve genellikle kopya oluşturucuyu) kullanılamaz hale getirdiğinizden ( boost::noncopyablebunları devralarak veya özel olarak bildirerek) emin olun.

Ancak, kullanıcı puanlarının zaten yorumladığı gibi, diğer nesneler için de aynı durum geçerli değildir. Burada, referans üyelerini kullanmak büyük bir sorun olabilir ve genellikle kaçınılmalıdır.


6

Herkes genel kurallar dağıtıyor gibi göründüğüm için iki tane sunacağım:

  • Asla, sınıf başvurusu olarak kullanım referanslarını kullanmayın. Kendi kodumda hiç yapmadım (kendime bu kuralda haklı olduğumu kanıtlamak dışında) ve bunu yapacağım bir durumu hayal edemiyorum. Anlambilim çok kafa karıştırıcıdır ve gerçekten referanslar için tasarlanmamıştır.

  • Temel türler dışında veya algoritmanın bir kopya gerektirdiği durumlarda, işlevlere parametreler iletirken daima, daima referanslar kullanın.

Bu kurallar basit ve beni iyi durumda tuttu. Akıllı işaretçiler (ancak, auto_ptr değil) başkalarına sınıf üyeleri olarak kullanma konusunda kurallar bırakıyorum.


2
Birincisi üzerinde tam olarak anlaşmayın, ancak anlaşmazlık mantığa değer veren bir konu değildir. +1
David Rodríguez - dribeas

(Yine de SO'nun iyi seçmenleri ile bu tartışmayı kaybediyor gibi görünüyoruz)
James Hopkin

2
Oy verdim çünkü "Asla, asla sınıf üyesi olarak referans kullanmayın" ve aşağıdaki gerekçe benim için doğru değil. Cevabımda açıklandığı gibi (ve en üstteki cevaba yorumda) ve kendi deneyimlerimden, bunun sadece iyi bir şey olduğu durumlar var. İyi bir şekilde kullandıkları bir şirkette işe alınana kadar senin gibi düşündüm ve bana yardım ettiği durumlar olduğunu açıkça ortaya koydu. Aslında bence asıl sorun, üyelerin programcının ne yaptığını anlamasını gerektirdiği için referansları kullanan sözdizimi ve gizli ima ile ilgili olduğunu düşünüyorum.
Klaim

1
Neil> Katılıyorum, ama oy vermemi sağlayan "görüş" değil, "asla" iddiası. Burada savunarak yine de yararlı görünmüyor gibi aşağı oyumu iptal edeceğim.
Klaim

2
Bu yanıt, referans üye semantiğinin ne hakkında kafa karıştırıcı olduğunu açıklayarak geliştirilebilir. Bu mantık önemlidir çünkü yüzünde işaretçiler daha anlamsal olarak belirsiz görünürler (başlatılmayabilirler ve size altta yatan nesnenin sahipliği hakkında hiçbir şey söylemezler).
weberc2

4

Evet: Bir başvuru başlatıldıktan sonra bir başvuru değiştirilmemesi gerektiğinden, başvuru içeren bir nesnenin atanması gerektiğini söylemek doğru mu?

Veri üyeleri için temel kurallarım:

  • asla referans kullanmayın, çünkü atamayı önler
  • sınıfınız silinmekten sorumluysa boost'un scoped_ptr (auto_ptr değerinden daha güvenli) kullanın
  • aksi halde bir işaretçi veya sabit işaretçi kullanın

2
İş nesnesinin bir görevlendirilmesi gerektiğinde bir vakayı bile adlandıramıyorum.
Mykola Golubyev

1
+1: Ben sadece auto_ptr üyelerine dikkat etmeyi ekliyorum - onlara sahip olan herhangi bir sınıfın açıkça tanımlanmış atama ve kopya yapısına sahip olması gerekir (oluşturulanların ne olması gerektiği pek olası değildir)
James Hopkin

auto_ptr (duyarlı) atamayı da engeller.

2
@ Mykola: İş nesnelerimin% 98'inin atama veya kopya oluşturucuya ihtiyaç duymadığı deneyimini de kazandım. Ben kopya c'tors ve operator = () hiçbir uygulama ile özel kılan bu sınıfların başlıklarına eklemek küçük bir makro ile bunu önlemek. Bu yüzden nesnelerin% 98'inde referanslar da kullanıyorum ve güvenli.
mmmmmmmm

1
Üye olarak yapılan başvurular, sınıf örneğinin yaşamının doğrudan diğer sınıfların (ya da aynı) örneklerin yaşamına bağlı olduğunu açıkça belirtmek için kullanılabilir. "Asla bir başvuru kullanmayın, çünkü atamayı önler" tüm sınıfların atamaya izin vermesi gerektiğini varsayar. Tüm sınıfların kopya işlemine izin vermesi gerektiği varsayımı gibidir ...
Klaim

2

Genellikle işaretçi boş veya değişebilir üye verileri yalnızca bir işaretçi kullanmak istiyorsunuz. Veri üyeleri için referanslar yerine işaretçileri tercih etmenin başka nedenleri var mı?

Evet. Kodunuzun okunabilirliği. Bir işaretçi, üyenin içerdiği bir nesne değil, bir referans (ironik bir şekilde :)) olduğunu daha açık hale getirir, çünkü onu kullandığınızda ona başvurmanız gerekir. Bazı insanların bunun eski moda olduğunu düşündüğünü biliyorum, ancak hala karışıklığı ve hataları önlediğini düşünüyorum.


1
Lakos ayrıca bunun için "Large-Scale C ++ Software Design" da tartışıyor.
Johan Kotlinski

Code Complete'in de bunu savunduğuna inanıyorum ve buna karşı değilim. Yine de aşırı zorlayıcı değil ...
markh44

2

Referans veri üyelerine karşı, sınıfınızdan kimin türeyeceğini ve ne yapmak isteyebileceklerini bilmediğinizden emin olmanızı tavsiye ederim. Başvurulan nesneyi kullanmak istemeyebilirler, ancak başvuru olarak geçerli bir nesne sağlamaya zorladınız. Bunu kendi kendime referans veri üyelerini kullanmayı bırakacak kadar yaptım.


0

Soruyu doğru anlarsam ...

İşaretçi yerine işlev parametresi olarak başvuru: İşaret ettiğiniz gibi, bir işaretçi işaretçinin kimin temizlendiğini / başlatılacağını açıklığa kavuşturmaz. İşaretçi istediğinizde paylaşılan bir noktayı tercih edin, bu C ++ 11'in bir parçasıdır ve işaretli verileri kabul eden sınıfın ömrü boyunca verilerin geçerliliği garanti edilmediğinde bir zayıf_ptr. Bir işlev parametresi olarak başvurunun kullanılması, başvurunun boş olmadığını garanti eder. Bunun üstesinden gelmek için dil özelliklerini değiştirmelisiniz ve gevşek top kodlayıcıları umursamıyoruz.

Aa üye değişkeni referansı: Veri geçerliliği ile ilgili olarak yukarıdaki gibi. Bu, daha sonra atıfta bulunulan verilere gösterilen verilerin geçerli olduğunu garanti eder.

Değişken geçerliliğin sorumluluğunu kodda daha erken bir noktaya taşımak, yalnızca sonraki kodu (örneğin A sınıfı) temizlemekle kalmaz, aynı zamanda kullanan kişiyi de netleştirir.

Biraz kafa karıştırıcı olan örneğinizde (gerçekten daha net bir uygulama bulmaya çalışacağım), B tarafından kullanılan A, B'nin ömrü boyunca garanti edilir, çünkü B, A'nın bir üyesidir, bu nedenle bir referans bunu güçlendirir ve (belki de) daha açıktır.

Ve her ihtimale karşı (bu, kod bağlamınızda herhangi bir anlam ifade etmeyeceği için düşük olasılıklı bir başlıktır), başka bir alternatif, referans verilmeyen, işaretçi olmayan bir A parametresi kullanmak, A'yı kopyalar ve paradigmayı işe yaramaz hale getirir - I gerçekten bunu bir alternatif olarak kastettiğini düşünmüyorum.

Ayrıca, bu, bir işaretçinin değiştirilebileceği başvurulan verileri değiştiremeyeceğinizi de garanti eder. Bir const işaretçisi, yalnızca verilere atıfta bulunulan / işaretçi değiştirilebilir değilse çalışır.

B için A parametresinin ayarlanması garanti edilmezse veya yeniden atanabiliyorsa işaretçiler yararlıdır. Ve bazen zayıf bir işaretçi uygulama için çok ayrıntılıdır ve çok sayıda insan ya bir shor_ptr'in ne olduğunu bilmez ya da onlardan hoşlanmaz.

Bu cevabı ayır, lütfen :)

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.