Std :: unique_ptr nasıl geçirilir?


85

C ++ 11'i kullanmaya ilk denememi yapıyorum unique_ptr; Bir sınıfa ait olan ancak oldukça sık geçen bir projemin içindeki polimorfik bir ham işaretçiyi değiştiriyorum.

Eskiden şöyle işlevlere sahiptim:

bool func(BaseClass* ptr, int other_arg) {
  bool val;
  // plain ordinary function that does something...
  return val;
}

Ancak kısa süre sonra şuna geçiş yapamayacağımı anladım:

bool func(std::unique_ptr<BaseClass> ptr, int other_arg);

Çünkü arayan, işleve işaretçi sahipliğini, benim istemediğim şeyi halletmek zorunda kalacaktı. Öyleyse, sorunumun en iyi çözümü nedir?

İşaretçiyi şu şekilde referans olarak geçirmeyi düşünüyorum:

bool func(const std::unique_ptr<BaseClass>& ptr, int other_arg);

Ama bunu yaparken kendimi çok rahatsız hissediyorum, çünkü ilk olarak zaten _ptrreferans olarak yazılmış bir şeyi, bir referansın referansı olarak geçmek içgüdüsel değil gibi görünüyor . İkincisi, çünkü işlev imzası daha da büyüyor. Üçüncüsü, üretilen kodda değişkenime ulaşmak için ardışık iki işaretçi indirimi gerekli olacaktır.

Yanıtlar:


99

İşlevin noktayı kullanmasını istiyorsanız, ona bir referans verin. İşlevi yalnızca bir tür akıllı işaretçiyle çalışacak şekilde bağlamanın bir nedeni yoktur:

bool func(BaseClass& base, int other_arg);

Ve çağrı yerinde operator*şunları kullanın :

func(*some_unique_ptr, 42);

Alternatif olarak, basebağımsız değişkenin boş olmasına izin veriliyorsa, imzayı olduğu gibi koruyun ve get()üye işlevini kullanın :

bool func(BaseClass* base, int other_arg);
func(some_unique_ptr.get(), 42);

4
Bu güzel, ama tartışmak std::unique_ptriçin camdan kurtuldun std::vector<std::unique_ptr>mu?
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

31

Kullanmanın avantajı ( std::unique_ptr<T>aramayı deleteveya delete[]açıkça hatırlamak zorunda olmamanın yanı sıra ), bir işaretçinin nullptr(temel) nesnenin geçerli bir örneğini işaret etmesini veya bir işaretçinin olduğunu garanti etmesidir. Soruna cevap sonra bu geri gelecektir, ancak ilk mesajdır DO dinamik olarak ayrılan nesnelerin ömrünü yönetmek için akıllı işaretçileri kullanır.

Şimdi, sorununuz aslında bunu eski kodunuzla nasıl kullanacağınızdır .

Benim önerim, mülkiyeti devretmek veya paylaşmak istemiyorsanız, her zaman nesneye referans vermelisiniz . İşlevinizi şu şekilde beyan edin ( constgerektiği gibi niteleyicilerle veya niteleyiciler olmadan ):

bool func(BaseClass& ref, int other_arg) { ... }

Ardından, durumu std::shared_ptr<BaseClass> ptrele alan nullptrya da bool func(...)sonucu hesaplamasını isteyen arayan kişi :

if (ptr) {
  result = func(*ptr, some_int);
} else {
  /* the object was, for some reason, either not created or destroyed */
}

Bu, arayan herhangi bir kişinin referansın geçerli olduğuna ve işlev gövdesinin yürütülmesi boyunca geçerli olmaya devam edeceğine söz vermesi gerektiği anlamına gelir .


Burada şiddetle gerektiğine inanıyoruz nedeni budur değil akıllı işaretçiler ham işaretçileri veya referanslar geçmektedir.

Ham bir işaretçi yalnızca bir bellek adresidir. (En az) 4 anlamdan birine sahip olabilir:

  1. İstediğiniz nesnenin bulunduğu bir bellek bloğunun adresi. ( iyi )
  2. Emin olabileceğiniz 0x0 adresi referans alınamaz ve "hiçbir şey" veya "nesne yok" semantiğine sahip olabilir. ( kötü )
  3. İşleminizin adreslenebilir alanının dışında olan bir bellek bloğunun adresi (bunu referans almak, umarız programınızın çökmesine neden olur). ( çirkin )
  4. Başvurusu yapılabilen, ancak beklediğinizi içermeyen bir bellek bloğunun adresi. İşaretçi yanlışlıkla değiştirilmiş olabilir ve şimdi başka bir yazılabilir adresi (sürecinizdeki tamamen başka bir değişkenin) işaret ediyor. Bu bellek konumuna yazmak, zaman zaman yürütme sırasında çok eğlencenin olmasına neden olur, çünkü işletim sistemi, orada yazmaya izin verildiği sürece şikayet etmeyecektir. ( Zoinks! )

Akıllı işaretçileri doğru şekilde kullanmak, genellikle derleme sırasında tespit edilemeyen ve genellikle yalnızca programınız çöktüğünde veya beklenmedik şeyler yaptığında çalışma zamanında karşılaştığınız oldukça korkutucu durumları (3 ve 4) hafifletir.

Akıllı işaretçileri bağımsız değişken olarak constaktarmanın iki dezavantajı vardır: Bir kopya yapmadan sivri uçlu nesnenin -ness değerini değiştiremezsiniz (bu, ek yük getirir shared_ptrve bunun için mümkün değildir unique_ptr) ve yine de ikinci ( nullptr) anlamıyla kalırsınız .

İkinci durumu tasarım açısından ( kötü ) olarak işaretledim . Bu, sorumluluk hakkında daha ince bir argüman.

Bir fonksiyonun nullptrparametresi olarak a almasının ne anlama geldiğini bir düşünün . Önce onunla ne yapacağına karar vermesi gerekiyor: Kayıp nesnenin yerine "sihirli" bir değer mi kullanmalı? davranışı tamamen değiştirip başka bir şey hesaplayın (nesneyi gerektirmeyen)? paniklemek ve bir istisna atmak? Dahası, işlev ham gösterici ile 2 veya 3 veya daha fazla argüman aldığında ne olur? Her birini kontrol etmeli ve davranışını buna göre uyarlamalıdır. Bu, gerçek bir sebep olmaksızın girdi doğrulamanın üstüne yepyeni bir seviye ekler.

Arayan, bu kararları vermek için yeterli bağlamsal bilgiye sahip olmalıdır, ya da başka bir deyişle, ne kadar çok bilirseniz , kötü olan daha az korkutucudur. Öte yandan işlev, arayanın işaret ettiği hafızanın amaçlandığı gibi çalışmasının güvenli olduğuna dair sözünü almalıdır. (Referanslar hala bellek adresleridir, ancak kavramsal olarak bir geçerlilik vaadini temsil eder.)


25

Martinho'ya katılıyorum, ancak referansla geçişin sahiplik anlamını belirtmenin önemli olduğunu düşünüyorum. Bence doğru çözüm, burada basit bir geçiş-referans kullanmaktır:

bool func(BaseClass& base, int other_arg);

C ++ 'da referansla geçirmenin yaygın olarak kabul edilen anlamı, işlevi çağıranın işlevi "burada, bu nesneyi ödünç alabilir, kullanabilir ve değiştirebilirsiniz (const değilse), ancak yalnızca işlev gövdesinin süresi. " Bu, hiçbir şekilde mülkiyet kuralları ile çelişmez, unique_ptrçünkü nesne sadece kısa bir süre için ödünç alınıyor, gerçek bir sahiplik devri gerçekleşmiyor (arabanızı birine ödünç verirseniz, tapuyu imzalar mısınız? ona mı?).

Dolayısıyla, referansı (veya hatta ham göstericiyi) dışarı çekmek kötü görünse de (tasarım açısından, kodlama uygulamaları vb.) unique_ptr, Aslında bu, tarafından belirlenen sahiplik kurallarına mükemmel bir şekilde uygun olduğu için değildir. unique_ptr. Ve sonra, tabii ki, temiz sözdizimi gibi başka güzel avantajlar da var, sadece a'nın sahip olduğu nesnelerde kısıtlama olmaması unique_ptrve benzeri.


0

Şahsen, bir işaretçiden / akıllı işaretçiden bir referans almaktan kaçınırım. Çünkü işaretçi ise ne olur nullptr? İmzayı buna değiştirirseniz:

bool func(BaseClass& base, int other_arg);

Kodunuzu boş işaretçi sapmalarından korumanız gerekebilir:

if (the_unique_ptr)
  func(*the_unique_ptr, 10);

Sınıf, işaretçinin tek sahibi ise, Martinho'nun alternatifinin ikincisi daha makul görünüyor:

func(the_unique_ptr.get(), 10);

Alternatif olarak kullanabilirsiniz std::shared_ptr. Ancak, sorumlu tek bir varlık varsa delete, std::shared_ptrgenel giderler karşılığını almaz.


Çoğu std::unique_ptrdurumda sıfır ek yüke sahip olduğunun farkında mısınız , değil mi?
lvella

1
Evet, bunun farkındayım. Bu yüzden ona std::shared_ptrgenel giderlerden bahsettim .
Guilherme Ferreira
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.