Paylaşılan işaretçileri bağımsız değişken olarak aktarma


89

Paylaşılan bir işaretçiye sarılmış bir nesne bildirirsem:

std::shared_ptr<myClass> myClassObject(new myClass());

sonra bunu bir yönteme argüman olarak aktarmak istedim:

DoSomething(myClassObject);

//the called method
void DoSomething(std::shared_ptr<myClass> arg1)
{
   arg1->someField = 4;
}

Yukarıdakiler, paylaşılan_pt'nin referans sayısını artırıyor ve her şey yolunda mı? Yoksa sallanan bir işaretçi mi bırakıyor?

Hala bunu yapmanız gerekiyor mu ?:

DoSomething(myClassObject.Get());

void DoSomething(std::shared_ptr<myClass>* arg1)
{
   (*arg1)->someField = 4;
}

Bence 2. yol daha verimli olabilir çünkü sadece 1 adresi kopyalamak zorunda (tüm akıllı işaretçinin aksine), ancak 1. yol daha okunabilir görünüyor ve performans sınırlarını zorlamayı beklemiyorum. Tehlikeli bir şey olmadığından emin olmak istiyorum.

Teşekkür ederim.


14
const std::shared_ptr<myClass>& arg1
Captain Obvlious

3
İkinci yol bozuk, ilki, gerçekten sahipliği paylaşmak için işlevinize ihtiyacınız varsa, deyimseldir. Ancak, DoSomethinggerçekten mülkiyeti paylaşmaya ihtiyaç var mı? Görünüşe göre bunun yerine bir referans almalı ...
ildjarn

8
@SteveH: Öyle değil, ama işleviniz gerçekten ihtiyaç duymuyorsa neden çağıranlara özel nesne sahipliği anlamını zorlasın? İşleviniz daha iyi olarak olmaz hiçbir şey yapmaz void DoSomething(myClass& arg1).
ildjarn

2
@SteveH: Akıllı işaretçilerin tüm amacı, sıradan nesne sahipliği sorunlarını halletmektir - eğer bunlara sahip değilseniz, ilk olarak akıllı işaretçiler kullanmamalısınız. ; -] shared_ptr<>Özellikle, sahipliği gerçekten paylaşmak için değerine göre geçmeniz gerekir .
ildjarn

4
İlgisiz: genel olarak, std::make_sharedsadece daha verimli değil, aynı zamanda kurucuya göre daha güvenlidirstd::shared_ptr .
R. Martinho Fernandes

Yanıtlar:


171

Bir işleve paylaşılan bir işaretçi iletmek istiyorum. Bana bununla yardım edebilir misin?

Elbette, sana bu konuda yardımcı olabilirim. C ++ 'da sahiplik anlamlarına dair biraz anlayışınız olduğunu varsayıyorum. Bu doğru mu?

Evet, konu ile oldukça rahatım.

İyi.

Tamam, shared_ptrtartışmak için sadece iki neden düşünebilirim :

  1. İşlev, nesnenin sahipliğini paylaşmak ister;
  2. İşlev, özellikle shared_ptrs üzerinde çalışan bazı işlemler yapar .

Hangisiyle ilgileniyorsun?

Genel bir cevap arıyorum, bu yüzden aslında ikisiyle de ilgileniyorum. Yine de 2 numaralı durumda ne demek istediğini merak ediyorum.

Bu tür işlevlerin örnekleri std::static_pointer_cast, özel karşılaştırıcılar veya yüklemleri içerir. Örneğin, bir vektörden tüm benzersiz paylaşılan_tr'yi bulmanız gerekiyorsa, böyle bir yüklemeye ihtiyacınız vardır.

Ah, işlevin aslında akıllı işaretçinin kendisini değiştirmesi gerektiğinde.

Kesinlikle.

Bu durumda referansla geçmemiz gerektiğini düşünüyorum.

Evet. İşaretçiyi değiştirmezse, const referansıyla geçmek istersiniz. Sahipliği paylaşmanız gerekmediği için kopyalamanıza gerek yoktur. Bu diğer senaryo.

Tamam anladım. Diğer senaryo hakkında konuşalım.

Sahipliği paylaştığınız kişi mi? Tamam. Sahipliği ile nasıl paylaşırsınız shared_ptr?

Kopyalayarak.

O zaman fonksiyonun bir kopyasını yapması gerekecek shared_ptr, doğru mu?

Açıkçası. Yani onu const'a referansla geçirip yerel bir değişkene kopyalayayım?

Hayır, bu bir kötümserlik. Referans ile aktarılırsa, işlevin kopyalamayı manuel olarak yapmaktan başka seçeneği kalmayacaktır. Değere göre aktarılırsa, derleyici kopya ve taşıma arasında en iyi seçimi seçecek ve bunu otomatik olarak gerçekleştirecektir. Öyleyse, değere göre aktarın.

İyi bir nokta. " Hız mı İstiyorsunuz? Değere Göre Geçiş." " Makalesini daha sık .

Bekle, ya örneğin işlev shared_ptrbir üye değişkeninde depolarsa? Bu gereksiz bir kopya oluşturmaz mı?

Fonksiyon, shared_ptrargümanı basitçe deposuna taşıyabilir . A'yı taşımak shared_ptrucuzdur çünkü herhangi bir referans sayısını değiştirmez.

Ah, iyi fikir.

Ama ben üçüncü bir senaryo düşünüyorum: Ya onu manipüle etmek shared_ptrya da sahipliği paylaşmak istemezseniz ?

Bu durumda, shared_ptrişlevle tamamen alakasızdır. Pointee'ı değiştirmek istiyorsanız, bir nokta alın ve arayanların istedikleri sahiplik anlamını seçmelerine izin verin.

Puanı referans olarak mı yoksa değer olarak mı almalıyım?

Olağan kurallar geçerlidir. Akıllı işaretçiler hiçbir şeyi değiştirmez.

Kopyalayacaksam değere göre geçir, kopyalamadan kaçınmak istiyorsam referans olarak geç.

Sağ.

Hmm. Sanırım başka bir senaryoyu daha unuttun. Ya sahipliği paylaşmak istiyorsam, ancak yalnızca belirli bir koşula bağlı olarak?

Ah, ilginç bir uç durum. Bunun sık sık olmasını beklemiyorum. Ancak bu gerçekleştiğinde, ya değere göre geçebilir ve ihtiyacınız yoksa kopyayı yok sayabilir ya da referans olarak geçebilir ve ihtiyacınız varsa kopyayı oluşturabilirsiniz.

İlk seçenekte bir yedek kopya riske giriyorum ve ikincisinde potansiyel bir hamleyi kaybediyorum. Pastayı ben de yiyemez miyim?

Bunun gerçekten önemli olduğu bir durumdaysanız, biri sabit değer referansı, diğeri de r değeri referansı alan iki aşırı yükleme sağlayabilirsiniz. Biri kopyalar, diğeri hareket eder. Mükemmel yönlendirme işlevi şablonu başka bir seçenektir.

Bunun tüm olası senaryoları kapsadığını düşünüyorum. Çok teşekkür ederim.


2
@Jon: Ne için? Bunların hepsi A yapmalı mıyım yoksa B mi yapmalıyım . Sanırım hepimiz nesneleri değere / referansa göre nasıl geçireceğimizi biliyoruz, değil mi?
sbi

9
Çünkü "Bu, kopyayı yapmak için const'a başvurarak geçeceğim anlamına mı geliyor? Hayır, bu bir kötümserlik" gibi bir düzyazı yeni başlayanlar için kafa karıştırıcıdır. Const'a başvurarak geçmek bir kopya oluşturmaz.
Jon

1
@Martinho "Pointee'yi manipüle etmek istiyorsan, bir pointee al" kuralına katılmıyorum. Bu yanıtta belirtildiği gibi , bir nesnenin geçici olarak sahiplenilmemesi tehlikeli olabilir. Kanımca varsayılan, işlev çağrısı süresi boyunca nesne geçerliliğini garanti etmek için geçici sahiplik için bir kopya geçirmek olmalıdır.
radman

@radman Bağlandığınız yanıtta hiçbir senaryo bu kuralı uygular, bu yüzden tamamen alakasızdır. Lütfen bana a'dan referans olarak geçtiğiniz shared_ptr<T> ptr;(yani void f(T&)ile çağrılan f(*ptr)) bir SSCCE yazın ve nesne çağrıdan daha uzun süre dayanmaz. Alternatif olarak, değere göre geçtiğiniz void f(T)ve sorunlu grevlere göre bir tane yazın.
R. Martinho Fernandes

@Martinho , basit kendi içerdiği doğru örnek için örnek koduma bakın . Örnek void f(T&)iplikler içindir ve konuları içerir. Sanırım benim sorunum, cevabınızın tonunun paylaşımlı_ptr'lerin sahipliğini devretmekten caydırmasıdır ki bu, onları kullanmanın en güvenli, en sezgisel ve en az karmaşık yolu olan. Yeni başlayanlara bir paylaşılan_ptr <> tarafından sahip olunan verilere bir referans çıkarmasını tavsiye etmeye son derece karşı olacağım, kötüye kullanım olasılıkları harika ve fayda çok az.
radman

22

İnsanların gereksiz yere ham işaretçileri fonksiyon parametreleri olarak kullanmaktan korktuğunu düşünüyorum. İşlev, işaretçiyi depolamayacaksa veya ömrünü başka bir şekilde etkilemeyecekse, bir ham işaretçi de aynı şekilde çalışır ve en düşük ortak paydayı temsil eder. Örneğin , a'yı parametre olarak unique_ptralan bir işleve shared_ptrdeğere göre veya const başvurusu ile nasıl geçireceğinizi düşünün ?

void DoSomething(myClass * p);

DoSomething(myClass_shared_ptr.get());
DoSomething(myClass_unique_ptr.get());

İşlev parametresi olarak ham bir işaretçi, gerçekten önemli olduğu yerde çağıran kodda akıllı işaretçiler kullanmanızı engellemez.


8
Bir işaretçi kullanıyorsanız, neden sadece bir referans kullanmıyorsunuz? DoSomething(*a_smart_ptr)
Xeo

5
@Xeo, haklısın - bu daha da iyi olurdu. Bazen yine de bir NULL işaretçisi olasılığına izin vermeniz gerekir.
Mark Ransom

1
Çıktı parametreleri olarak ham işaretçileri kullanma kuralınız varsa, kodu çağırırken bir değişkenin değişebileceğini tespit etmek daha kolaydır, örneğin: karşılaştırma fun(&x) için fun(x). İkinci örnekte x, değere göre veya sabit ref olarak iletilebilir. Yukarıda belirtildiği gibi nullptr
Andreas Magnusson

2
@AndreasMagnusson: Bu parametre olarak işaretçi kuralı, başka bir kaynaktan iletilen işaretçilerle uğraşırken yanlış bir güvenlik hissi verebilir. Çünkü bu durumda çağrı eğlencelidir (x) ve * x değiştirilir ve kaynakta sizi uyaracak & x yoktur.
Zan Lynx

ya işlev çağrısı, arayanın kapsamını aşarsa? Paylaşılan ptr'nin yıkıcısının çağrılmış olma olasılığı vardır ve şimdi işlev silinmiş belleğe erişmeye çalışarak UB ile sonuçlanacaktır.
Sridhar Thiagarajan

4

Evet, bir shared_ptr <> hakkındaki tüm fikir, birden çok örneğin aynı ham işaretçiyi tutabileceği ve temeldeki belleğin yalnızca shared_ptr <> 'nin son örneği yok edildiğinde serbest bırakılacağıdır.

Bir shared_ptr <> için göstericiden kaçınırım çünkü bu, siz şimdi raw_pointers ile yeniden uğraştığınız için amacınızı bozar.


2

İlk örneğinizde değere göre geçmek güvenlidir, ancak daha iyi bir deyim vardır. Mümkün olduğunda const referansı ile geçin - akıllı işaretçilerle uğraşırken bile evet derdim. İkinci örneğiniz tam olarak bozuk değil ama çok !???. Aptalca, hiçbir şeyi başaramamak ve akıllı işaretçilerin amacının bir kısmını bozmak ve bir şeyleri değiştirmeye ve değiştirmeye çalıştığınızda sizi acı dolu bir dünyada bırakacak.


3
Ham işaretçiyi kastediyorsanız, hiç kimse işaretçi ile geçmeyi savunmuyor. Sahiplik çekişmesi yoksa referans olarak geçmek kesinlikle deyimseldir. Burada önemli olan nokta, mülkiyeti gerçekten paylaşmak için bir kopya shared_ptr<>almanız gerektiğidir ; bir referansınız veya işaretçiniz shared_ptr<>varsa, hiçbir şey paylaşmıyorsunuzdur ve normal bir referans veya işaretçiyle aynı ömür boyu sorunlara maruz kalırsınız.
ildjarn

2
@ildjarn: Bu durumda sabit bir referans iletmek iyidir. shared_ptrArayanın orijinalinin , işlev çağrısından daha uzun süre dayanması garanti edilir, bu nedenle işlev shared_ptr<> const &,. O henüz sıra işaretçi saklamak gerekiyorsa, (bir kopyası yerine bir referansa sahip daha yapılmalıdır bu noktada) kopyalamak gerekecektir, fakat kopyalama maliyet uğramak gerek yoktur sürece yapmanız gereken o. Bu durumda sürekli bir referansta
bulunmaya

3
@David: " Bu durumda sabit bir referans geçmek iyidir. " Herhangi bir mantıklı API'de değil - neden bir API, normal bir const referansı almaktan çok faydalanmadığı bir akıllı işaretçi türünü zorunlu kılıyor ? Bu neredeyse sabit doğruluk eksikliği kadar kötü. Eğer amacınız teknik olarak hiçbir şeye zarar vermiyorsa, kesinlikle katılıyorum, ama bunun yapılacak doğru şey olduğunu düşünmüyorum.
ildjarn

2
... Öte yandan, işlevin nesnenin ömrünü uzatması gerekmiyorsa, o zaman sadece shared_ptrarayüzden kaldırabilir constve sivri uçlu nesneye düz ( ) bir referans iletebilirsiniz .
David Rodríguez - dribeas

3
@David: Ya API tüketiciniz başlangıçta kullanmıyorsa shared_ptr<>? Şimdi API'niz, arayan kişiyi sadece kullanmak için nesne yaşam boyu anlamlarını anlamsız bir şekilde değiştirmeye zorladığından, gerçekten sadece baş belası. "Teknik olarak hiçbir şeye zarar vermiyor" demekten başka bunda savunmaya değer bir şey görmüyorum. 'Ortak mülkiyete ihtiyacınız yoksa kullanma shared_ptr<>' konusunda tartışmalı bir şey görmüyorum .
ildjarn

0

işlevinizde DoSomething , bir sınıf örneğinin veri üyesini myClass değiştiriyorsunuz, böylece değiştirdiğiniz şey yönetilen (ham işaretçi) nesnedir (shared_ptr) nesnesi değil. Bu, bu işlevin dönüş noktasında, yönetilen ham işaretçiye giden tüm paylaşılan işaretçilerin veri üyelerini göreceği anlamına gelir:myClass::someField farklı bir değere değiştirildi.

bu durumda, bir nesneyi, onu değiştirmediğiniz garantisiyle bir işleve geçirirsiniz (sahip olunan nesne değil, shared_ptr hakkında konuşursunuz).

Bunu ifade etmek için kullanılan deyim: bir const ref, bunun gibi

void DoSomething(const std::shared_ptr<myClass>& arg)

Aynı şekilde, işlevinizin kullanıcısına, ham işaretçinin sahiplerinin listesine başka bir sahip eklemediğinizi garanti ediyorsunuz. Ancak, ham işaretçinin gösterdiği temel nesneyi değiştirme olanağını korudunuz.

CAVEAT: Bu, bir şekilde shared_ptr::resetsiz işlevinizi çağırmadan önce birisi ararsa ve o anda raw_ptr'ye sahip olan son paylaşılan_ptr ise, o zaman nesneniz yok edilir ve işleviniz sarkan bir İşaretçiyi yok edilmiş nesneye yönlendirir. ÇOK TEHLİKELİ!!!

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.