Ne zaman işaretçi kullanırım?


228

Tamam, son kez C ++ 'ı bir yaşam için yazdım std::auto_ptr, std lib'in mevcut olduğu ve boost::shared_ptrtüm öfke oldu. Ben gerçekten sağlanan diğer akıllı işaretçi türleri artış içine baktım. C ++ 11'in artık bazı türlerde artış sağladığını anlıyorum, ancak hepsini değil.

Peki birisinin hangi akıllı işaretçiyi ne zaman kullanacağını belirlemek için basit bir algoritması var mı? Tercihen aptal işaretçiler (ham işaretçiler gibi T*) ve destek akıllı işaretçilerin geri kalanı ile ilgili tavsiyeler dahil . (Böyle bir şey bu harika olurdu).



1
Gerçekten birinin bu STL seçim akış şeması gibi güzel kullanışlı bir akış şeması ile gelmesini umuyorum .
Alok

1
@Als: Oh, bu gerçekten hoş bir şey! SSS yaptým.
sbi

6
@ Duplicalica Bu kopya olmaya bile yakın değil. Bağlantılı soru diyor "Ben ne zaman kullanmalıyım bir akıllı işaretçi" ve bu sorudur "Ben kullanırım zaman bu akıllı işaretçileri?" yani bu, standart akıllı işaretçilerin farklı kullanımlarını kategorize etmektedir. Bağlantılı soru bunu yapmaz. Fark görünüşte küçük ama büyük bir fark.
Rapptz

Yanıtlar:


183

Paylaşılan sahiplik: ve benimsenen standart hemen hemen onların aynıdır Boost muadilleri . Bir kaynağı paylaşmanız gerektiğinde kullanın ve hangisinin hayatta kalacağını bilmeyin. Paylaşılan kaynağı, döngüleri kırmak için değil, ömrünü etkilemeden gözlemlemek için kullanın . İle döngüler normalde olmamalıdır - iki kaynak birbirine sahip olamaz.
shared_ptrweak_ptrweak_ptrshared_ptr

Boost ek olarak teklifler shared_array, ki bu uygun bir alternatif olabilir shared_ptr<std::vector<T> const>.

Daha sonra, intrusive_ptrkaynağınız zaten referans sayımlı yönetim sunuyorsa ve bunu RAII prensibine uyarlamak istiyorsanız, hafif bir çözüm olan Boost teklifleri sunar . Bu standart tarafından kabul edilmedi.

Benzersiz sahiplik:
Boost ayrıca, scoped_ptrkopyalanamayan ve bir silme belirleyemediğiniz bir a'ya sahiptir. std::unique_ptrolduğu boost::scoped_ptrsteroid ve olmalı sen zeki işaretçi gerektiğinde varsayılan seçim . Şablon argümanlarında bir silme belirtmenizi sağlar ve aksine hareket edebilirboost::scoped_ptr . Ayrıca, kopyalanabilir tiplere (açıkça) ihtiyaç duyulan işlemleri kullanmadığınız sürece STL kaplarında da tamamen kullanılabilir.

Yine, Boost'un bir dizi sürümüne sahip olduğunu unutmayın: scoped_arraystandart , işaretçi yerine std::unique_ptr<T[]>kısmi uzmanlık gerektirerek birleştirilir ( r ile). ayrıca sunuyordelete[]deletedefault_deletestd::unique_ptr<T[]>operator[] yerine operator*ve operator->.

Bunu not et std::auto_ptr standardında hala, ama bir kullanımdan kaldırıldı . §D.10 [depr.auto.ptr]

Sınıf şablonu auto_ptrkullanımdan kaldırıldı. [ Not: Sınıf şablonu unique_ptr(20.7.1) daha iyi bir çözüm sağlar.—End not ]

Sahiplik yok: Sahip olmayan
referanslar için aptal işaretçiler (ham işaretçiler) veya referanslar kullanın Kaynaklara için ve kaynağın referans nesnesinden / kapsamından daha fazla çıkacağını bildiğinizde . Nullabilite veya yeniden yüklenebilirliğe ihtiyacınız olduğunda referansları tercih edin ve ham işaretçiler kullanın.

Eğer bir kaynağa olmayan bir sahibi başvuru istiyorum, ama kaynak nesneyi gömecek eğer referanslar bu bir de kaynak paketi olduğunu bilmiyorsanız shared_ptrve kullanımı bir weak_ptr- ebeveyn eğer test edebilirsiniz shared_ptrile hayatta lockhangi edecek, bir dönüş shared_ptrkaynak hala varsa boş olmayan olduğunu. Kaynağın ölüp ölmediğini test etmek istiyorsanız kullanın expired. İkisi benzer gelebilir, ancak eşzamanlı yürütme karşısında çok farklıdır.expired sadece bu tek ifade için dönüş değerini garanti eder. Gibi masum bir test

if(!wptr.expired())
  something_assuming_the_resource_is_still_alive();

potansiyel bir yarış koşuludur.


1
Sahiplik olmaması durumunda, referansların kesilmeyeceği bir sahipliğe ve yeniden yüklenmeye ihtiyaç duymadığınız sürece, muhtemelen işaretçileri referans almayı tercih etmelisiniz , o zaman bile orijinal nesneyi bir shared_ptrve sahip olmayan bir işaretçi olarak yeniden yazmayı düşünebilirsiniz . a weak_ptr...
David Rodríguez - dribeas

2
İşaretçiye gönderme yapmak yerine işaretçi yerine referans demek istedim . Sahiplik yoksa, yeniden yüklenebilirliğe (veya nullabiliteye, ancak sıfırlanamayan nullabilite oldukça sınırlı) gerekmedikçe, ilk önce bir işaretçi yerine düz bir referans kullanabilirsiniz.
David Rodríguez - dribeas

1
@David: Ah, anlıyorum. :) Evet, referanslar bunun için fena değil, bu gibi durumlarda ben şahsen onları tercih ederim. Onları ekleyeceğim.
Xeo

1
@Xeo: shared_array<T>bir alternatiftir shared_ptr<T[]>değil shared_ptr<vector<T>>: o büyüyemez.
R. Martinho Fernandes

1
@GregroyCurrie: Bu ... tam olarak yazdığım şey bu mu? Bunun potansiyel bir yarış durumu örneği olduğunu söyledim.
Xeo

127

Hangi akıllı işaretçiyi kullanacağınıza karar vermek bir sahiplenme meselesidir . Kaynak yönetimi söz konusu olduğunda, A nesnesinin sahibi nesnesinin B ömrünün kontrolünde olması durumunda nesnesinin B olur. Örneğin, üye değişkenlerin ömrü nesnenin ömrüne bağlı olduğu için üye değişkenler kendi nesnelerine aittir. Nesnenin sahiplenme biçimine göre akıllı işaretçiler seçersiniz.

Bir yazılım sistemindeki sahipliğin, yazılım dışında düşündüğümüz gibi sahiplikten ayrı olduğunu unutmayın. Örneğin, bir kişi evine "sahip olabilir", ancak bu mutlaka bir Personnesnenin bir nesnenin ömrü boyunca kontrol sahibi olduğu anlamına gelmez House. Bu gerçek dünya kavramlarını yazılım kavramlarıyla karıştırmak, kendinizi bir deliğe programlamanın kesin bir yoludur.


Nesnenin tek sahipliğiniz varsa kullanın std::unique_ptr<T>.

Nesnenin sahipliğini paylaştıysanız ...
- Sahiplikte herhangi bir döngü yoksa kullanın std::shared_ptr<T>.
- Çevrimler varsa, bir "yön" tanımlayın ve std::shared_ptr<T>bir yönde kullanın vestd::weak_ptr<T> diğer .

Nesne size aitse, ancak sahip sahibi olma potansiyeli yoksa normal işaretçiler kullanın T* (örn. Üst işaretçiler).

Nesne size sahipse (veya başka bir şekilde varoluş garantisi varsa), başvuruları kullanın T&.


Uyarı: Akıllı işaretçilerin maliyetlerinin farkında olun. Bellek veya performansla sınırlı ortamlarda, belleği yönetmek için yalnızca daha manuel bir şema ile normal işaretçileri kullanmak yararlı olabilir.

Maliyetler:

  • Özel bir silmeniz varsa (örneğin, ayırma havuzları kullanırsanız), bu işaretçi başına manüel olarak silme işleminden kolayca kaçınılabilecek bir ek yüke neden olur.
  • std::shared_ptrkopyada bir referans sayımı artışının ek yükü, ayrıca imhada bir azalma ve ardından tutulan nesnenin silinmesiyle 0 sayım denetimi yapılır. Uygulamaya bağlı olarak, bu kodunuzu şişirebilir ve performans sorunlarına neden olabilir.
  • Derleme zamanı. Tüm şablonlarda olduğu gibi akıllı işaretçiler de derleme sürelerine olumsuz katkıda bulunur.

Örnekler:

struct BinaryTree
{
    Tree* m_parent;
    std::unique_ptr<BinaryTree> m_children[2]; // or use std::array...
};

İkili bir ağacın ana ağacı yoktur, ancak bir ağacın varlığı ana ağacının (veya nullptrkök için) varlığını ima eder , böylece normal bir işaretçi kullanır. İkili bir ağacın (değer semantiği olan) çocuklarının tek mülkiyeti vardır, yani bunlar std::unique_ptr.

struct ListNode
{
    std::shared_ptr<ListNode> m_next;
    std::weak_ptr<ListNode> m_prev;
};

Burada, liste düğümü sonraki ve önceki listelerinin sahibidir, bu nedenle bir yön tanımlar ve döngüyü kırmak shared_ptriçin sonraki ve weak_ptrönceki için kullanırız.


3
İkili ağaç örneği için, bazı insanlar shared_ptr<BinaryTree>çocuklar ve weak_ptr<BinaryTree>ebeveyn ilişkisi için kullanılmasını önerebilir .
David Rodríguez - dribeas

@ DavidRodríguez-dribeas: Ağacın değer anlambilimine sahip olup olmadığına bağlıdır. Kaynak ağaç yok edildikten sonra bile insanlar ağacınızı dışarıdan referans alacaklarsa, evet, paylaşılan / zayıf işaretçi kombinasyonu en iyisi olacaktır.
Peter Alexander

Bir nesnenin sahibi sizseniz ve var olması garantileniyorsa neden bir referans vermiyorsunuz?
Martin York

1
Referans kullanırsanız, tasarımı engelleyebilecek veya engelleyemeyecek ebeveyni hiçbir zaman değiştiremezsiniz. Ağaçları dengelemek için bu engel olur.
Mooing Duck

3
+1 ancak ilk satıra "sahiplik" tanımını eklemelisiniz. Sık sık kendimi bunun, alana daha özgü bir anlamda sahiplikle değil, nesnenin yaşamı ve ölümü ile ilgili olduğunu açıkça belirtmek zorunda buldum.
Klaim

19

unique_ptr<T>Referans sayımına ihtiyacınız olmadığı durumlar dışında her zaman kullanın , bu durumda kullanın shared_ptr<T>(ve çok nadir durumlarda, weak_ptr<T>referans döngülerini önlemek için). Neredeyse her durumda, devredilebilir benzersiz sahiplik gayet iyi.

Ham işaretçiler: Yalnızca kovaryant dönüşlere ihtiyacınız varsa, olabilecek olmayan işaret. Aksi takdirde çok faydalı değiller.

Dizi işaretçileri: sonucu otomatik olarak çağıran unique_ptrbir uzmanlığa sahiptir , böylece örneğin güvenle yapabilirsiniz . yine de özel bir silmeye ihtiyacınız vardır, ancak özel bir paylaşılan veya benzersiz dizi işaretçisine ihtiyacınız olmaz. Tabii ki, bu tür şeyler genellikle en iyi şekilde değiştirilir . Ne yazık ki bir dizi erişim işlevi sağlamaz, bu nedenle yine de manuel olarak aramanız gerekir , ancak ve yerine sağlar . Her durumda, kendinizi kontrol etmelisiniz. Bu , genel avantajı ve Boost bağımlılığının olmaması ve kazananları tekrar tartışmasa da, biraz daha az kullanıcı dostu hale getirir .T[]delete[]unique_ptr<int[]> p(new int[42]);shared_ptrstd::vectorshared_ptrget()unique_ptr<T[]>operator[]operator*operator->shared_ptrunique_ptrshared_ptr

Kapsamlı işaretçiler: unique_ptrTıpkı gibi auto_ptr.

Gerçekten bundan başka bir şey yok. Hareket semantiği olmayan C ++ 03'te bu durum çok karmaşıktı, ancak C ++ 11'de tavsiye çok basit.

intrusive_ptrVeya gibi diğer akıllı işaretçilerin hala kullanımları vardır interprocess_ptr. Ancak, bunlar çok niş ve genel durumda tamamen gereksiz.


Ayrıca, yineleme için ham işaretçiler. Ve tamponun arayana ait olduğu çıkış parametresi tamponları için.
Ben Voigt

Hmm, bunu okuma şeklim, hem kovaryant dönüş hem de sahip olmama durumları. Kavşaktan ziyade birliği kastediyorsanız, yeniden yazma iyi olabilir. Ayrıca yinelemenin de özel bir değere değdiğini söyleyebilirim.
Ben Voigt

2
std::unique_ptr<T[]>ve operator[]yerine sağlar . Yine de kendinizi kontrol altına almak zorunda olduğunuz doğrudur. operator*operator->
Xeo

8

Ne zaman kullanılacağı unique_ptr:

  • Fabrika yöntemleri
  • İşaretçi olan üyeler (pimpl dahil)
  • İşaretleyicileri stl kirleticilere depolama (hamleleri önlemek için)
  • Büyük yerel dinamik nesnelerin kullanımı

Ne zaman kullanılacağı shared_ptr:

  • Nesneleri iş parçacıkları arasında paylaşma
  • Nesneleri genel olarak paylaşma

Ne zaman kullanılacağı weak_ptr:

  • Genel referans görevi gören büyük harita (örneğin tüm açık soketlerin haritası)

Düzenlemek ve daha fazlasını eklemek için çekinmeyin


Sen senaryoları verirken cevabını daha çok seviyorum.
Nicholas Humphrey
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.