Std :: weak_ptr ne zaman yararlıdır?


Yanıtlar:


231

Bunun iyi bir örneği önbellek olabilir.

Son erişilen nesneler için onları bellekte tutmak istersiniz, böylece onlara güçlü bir işaretçi tutarsınız. Düzenli olarak, önbelleği tarar ve son zamanlarda hangi nesnelere erişilmediğine karar verirsiniz. Bunları hafızada tutmanıza gerek yok, bu yüzden güçlü işaretçiden kurtulun.

Peki ya bu nesne kullanımdaysa ve başka bir kod ona güçlü bir işaretçi içeriyorsa? Önbellek nesneye olan tek işaretçisinden kurtulursa, bir daha asla bulamaz. Böylece önbellek, bellekte kalıp kalmayacaklarını bulması gereken nesnelere zayıf bir işaretçi tutar.

Zayıf bir işaretçinin yaptığı tam olarak budur - eğer hala etrafındaysa bir nesneyi bulmanıza izin verir, ancak başka bir şeye ihtiyaç duymazsa onu tutmaz.


8
Yani std :: wake_ptr yalnızca başka bir işaretçinin işaret ettiği yeri gösterebilir ve sivri uçlu nesne artık başka bir işaretçi tarafından silindiğinde / işaret edilmediğinde nullptr'e işaret edebilir mi?

27
@RM: Temel olarak evet. Zayıf bir işaretçiniz olduğunda, onu güçlü bir işaretçiye yükseltmeye çalışabilirsiniz. Bu nesne hala mevcutsa (en az bir güçlü işaretçi hala var olduğu için) bu işlem başarılı olur ve size güçlü bir işaretçi verir. Bu nesne yoksa (tüm güçlü işaretçiler gittiğinden), bu işlem başarısız olur (ve genellikle zayıf işaretçiyi atarak tepki verirsiniz).
David Schwartz

12
Güçlü bir işaretçi bir nesneyi canlı tutarken, bir thin_ptr nesnenin yaşam süresi ile uğraşmadan ona bakabilir.
Vivandiere

3
En azından birkaç kez kullandığım başka bir örnek, gözlemcileri uygularken, bazen konunun zayıf işaretçiler listesini tutması ve kendi liste temizliğini yapması uygun olur. Gözlemcileri silindiklerinde açıkça kaldırmak biraz çaba tasarrufu sağlar ve daha önemli olan şey, genellikle olayları çok basitleştiren gözlemcileri yok ederken mevcut konular hakkında bilgi sahibi olmanız gerekmez.
Jason C

3
Bekle, bir paylaşılan_ptr tutan ve sadece bellekten silinmesi gerektiğinde listeden kaldırmak önbellek ile sorun nedir? Herhangi bir kullanıcı bir shared_ptr dosyasını aynı şekilde tutar ve önbelleğe alınan kaynak, tüm kullanıcılar bununla birlikte yapılır yapılmaz silinir.
rubenvb

299

std::weak_ptrsarkan işaretçi problemini çözmenin çok iyi bir yoludur . Sadece ham işaretçiler kullanarak, referans alınan verilerin serbest bırakılıp bırakılmadığını bilmek imkansızdır. Bunun yerine, std::shared_ptrverileri yönetmesine izin vererek ve std::weak_ptrkullanıcılara veri sağlayarak , kullanıcılar expired()veya öğelerini arayarak verilerin geçerliliğini denetleyebilir lock().

std::shared_ptrTüm std::shared_ptrörnekler kaldırılmadan önce kaldırılmayan verilerin sahipliğini tüm örnekler paylaştığından bunu tek başınıza yapamazsınız std::shared_ptr. İşte sarkan işaretçiyi kullanarak kontrol etmek için bir örnek lock():

#include <iostream>
#include <memory>

int main()
{
    // OLD, problem with dangling pointer
    // PROBLEM: ref will point to undefined data!

    int* ptr = new int(10);
    int* ref = ptr;
    delete ptr;

    // NEW
    // SOLUTION: check expired() or lock() to determine if pointer is valid

    // empty definition
    std::shared_ptr<int> sptr;

    // takes ownership of pointer
    sptr.reset(new int);
    *sptr = 10;

    // get pointer to data without taking ownership
    std::weak_ptr<int> weak1 = sptr;

    // deletes managed object, acquires new pointer
    sptr.reset(new int);
    *sptr = 5;

    // get pointer to new data without taking ownership
    std::weak_ptr<int> weak2 = sptr;

    // weak1 is expired!
    if(auto tmp = weak1.lock())
        std::cout << *tmp << '\n';
    else
        std::cout << "weak1 is expired\n";

    // weak2 points to new data (5)
    if(auto tmp = weak2.lock())
        std::cout << *tmp << '\n';
    else
        std::cout << "weak2 is expired\n";
}

1
Tamam, sanki yerel olarak bir (sahip olunan) işaretçiyi null (belleği sil) olarak ayarlarsanız, aynı belleğe giden tüm diğer (zayıf) işaretçiler de null
Pat-Laugh

std::weak_ptr::lockstd::shared_ptryönetilen nesnenin sahipliğini paylaşan yeni bir öğe oluşturur .
Sahib Yar

129

Başka bir cevap, umarım daha basittir. (diğer Google çalışanları için)

Varsayalım TeamveMember nesneleriniz var.

Açıkçası bu bir ilişkidir: Teamnesnenin işaretçisi olacaktır Members. Muhtemelen üyelerin aynı zamandaTeam nesnelerine .

Sonra bir bağımlılık döngüsü var. Eğer kullanırsanshared_ptr , nesnelere referansla vazgeçtiğinizde nesneler artık otomatik olarak serbest bırakılmaz, çünkü birbirlerine döngüsel bir şekilde başvururlar. Bu bir bellek sızıntısı.

Bunu kullanarak kırarsınız weak_ptr. "Sahip" tipik olarak kullanır shared_ptrve "sahip olunan" weak_ptröğesi üst öğeye a kullanır ve geçici olarakshared_ptr öğeye erişmesi gerektiğinde .

Zayıf bir ptr saklayın:

weak_ptr<Parent> parentWeakPtr_ = parentSharedPtr; // automatic conversion to weak from shared

sonra gerektiğinde kullan

shared_ptr<Parent> tempParentSharedPtr = parentWeakPtr_.lock(); // on the stack, from the weak ptr
if( !tempParentSharedPtr ) {
  // yes, it may fail if the parent was freed since we stored weak_ptr
} else {
  // do stuff
}
// tempParentSharedPtr is released when it goes out of scope

1
Bu nasıl bir bellek sızıntısı? Eğer takım yok edilirse, üyelerini yok eder, böylece shared_ptr ref sayısı 0 olur ve ayrıca yok edilir mi?
paulm

4
@paulm Team "üyelerini" yok etmeyecek. Bütün mesele shared_ptrmülkiyeti paylaşmaktır, bu yüzden hiç kimse hafızayı boşaltmak için özel bir sorumluluğa sahip değildir, artık kullanılmadığında otomatik olarak serbest bırakılır. Bir döngü olmadığı sürece ... Aynı oyuncuyu paylaşan birkaç takımınız olabilir (geçmiş takımlar?). Takım nesnesi üyelere "sahipse", shared_ptrbaşlamak için a kullanmaya gerek yoktur .
Offirmo

1
Onları yok etmeyecek, ancak shared_ptr onunla kapsam dışına çıkacak, use_count değerini azaltacak ve bu noktada use_count 0 olacak ve bu nedenle shared_ptr işaretini silecek mi?
paulm

2
@paulm Haklısın. Ancak, bu örnekte, takıma shared_ptr"ekip üyeleri" tarafından da atıfta bulunulduğu için ne zaman imha edilecek? Tanımladığınız şey, döngü olmayan bir durumdur.
Offirmo

14
O kadar da kötü değil, sanırım. Bir üye birçok takıma aitse, bir referans kullanmak işe yaramaz.
Mazyod

22

İşte @jleahy tarafından bana verilen bir örnek: Bir zamanlar eşzamanlı olarak yürütülen ve bir tarafından yönetilen bir görev koleksiyonunuz olduğunu varsayalım std::shared_ptr<Task>. Bu görevlerle belirli aralıklarla bir şeyler yapmak isteyebilirsiniz, böylece bir timer olayı a'yı geçebilir std::vector<std::weak_ptr<Task>>ve görevlere bir şeyler verebilir. Bununla birlikte, aynı anda bir görev aynı anda artık gerekli olmadığına ve öleceğine karar vermiş olabilir. Böylece zamanlayıcı, zayıf işaretçiden paylaşılan bir işaretçi yaparak ve boş değilse, o paylaşılan işaretçiyi kullanarak görevin hala hayatta olup olmadığını kontrol edebilir.


4
: Kulağa iyi bir örnek gibi gelebilir, ama lütfen biraz daha örnek verebilir misiniz? Ben bir görev bittiğinde, zaten periyodik kontrol olmadan std :: vector <std :: weak_ptr <Görev>> kaldırılması gerektiğini düşünüyorum. Bu yüzden std :: vector <std :: weak_ptr <>> burada çok yardımcı olup olmadığından emin değilim.
Gob00st

Kuyruklarla benzer yorum: nesneleriniz olduğunu ve bunları bir kaynak için kuyruğunuzu varsayalım, beklerken nesneler silinebilir. Bu nedenle, poor_ptrs kuyruğunu sıralarsanız, o kuyruktaki girişleri silmekle uğraşmanıza gerek kalmaz. Weak_ptrs geçersiz kılınır ve kodlanırken atılır.
zzz777

1
@ zzz777: Nesneleri geçersiz kılan mantık, gözlemcilerin kuyruğunun veya vektörünün varlığından bile haberdar olmayabilir. Bu yüzden gözlemci zayıf işaretçiler üzerinde ayrı bir döngü gerçekleştirir, hala hayatta olanlar üzerinde hareket eder ve ölüleri konteynırdan
çıkarır

1
@KerekSB: evet ve kuyruk durumunda ayrı bir döngüye bile gerek yok - kaynak mevcutsa, geçerli olana (varsa) sahip olana kadar süresi dolan zayıf_ptr'leri atın.
zzz777

Ayrıca iş parçacığı kendilerini koleksiyondan kaldırmak olabilir, ancak bu bir bağımlılık oluşturur ve kilitleme gerektirir.
curiousguy

16

Eşzamansız bir işleyici çağrıldığında bir hedef nesnenin hala var olduğu garanti edilmediğinde Boost.Asio ile kullanışlıdırlar. Hüner, lambda yakalamalarını weak_ptrkullanarak asenkronik işleyici nesnesine a bağlamaktır std::bind.

void MyClass::startTimer()
{
    std::weak_ptr<MyClass> weak = shared_from_this();
    timer_.async_wait( [weak](const boost::system::error_code& ec)
    {
        auto self = weak.lock();
        if (self)
        {
            self->handleTimeout();
        }
        else
        {
            std::cout << "Target object no longer exists!\n";
        }
    } );
}

Bu bir varyantı olan self = shared_from_this()bekleyen bir asenkron işleyicisi nerede genellikle Boost.Asio örneklerinde görüldüğü deyim değil , henüz hedef nesnenin ömrünü uzatmak hedef nesnenin silinirse hala güvenlidir.


Bu cevabı bulmak neden bu kadar uzun sürdü ... PS, yakalamanızı kullanmıyorsunuzthis
Orwellophile

@Orwellophile düzeltildi. self = shared_from_this()İşleyici aynı sınıf içindeki yöntemleri çağırdığında deyimi kullanırken alışkanlık gücü .
Emile Cormier

16

shared_ptr : gerçek nesneyi tutar.

poor_ptr : lockgerçek sahibe bağlanmak için kullanılır veya shared_ptraksi takdirde NULL döndürür.

zayıf ptr

Kabaca söylemek gerekirse, weak_ptrrol konut ajansı rolüne benzer . Acentesiz, kiralık bir ev almak için şehirdeki rastgele evleri kontrol etmemiz gerekebilir. Temsilciler, sadece hala erişilebilir ve kiralanabilen evleri ziyaret ettiğimizden emin olurlar .


14

weak_ptrözellikle birim testlerinde bir nesnenin doğru silinmesini kontrol etmek de iyidir. Tipik kullanım durumu şöyle görünebilir:

std::weak_ptr<X> weak_x{ shared_x };
shared_x.reset();
BOOST_CHECK(weak_x.lock());
... //do something that should remove all other copies of shared_x and hence destroy x
BOOST_CHECK(!weak_x.lock());

13

İşaretçileri kullanırken, mevcut farklı işaretçi türlerini ve her birini kullanmanın ne zaman mantıklı olduğunu anlamak önemlidir. Aşağıdaki gibi iki kategoride dört tür işaretçi vardır:

  • Ham işaretçiler:
    • Ham İşaretçi [ie SomeClass* ptrToSomeClass = new SomeClass();]
  • Akıllı işaretçiler:
    • Benzersiz İşaretçiler [ie
      std::unique_ptr<SomeClass> uniquePtrToSomeClass ( new SomeClass() );
      ]
    • Paylaşılan İşaretçiler [ie
      std::shared_ptr<SomeClass> sharedPtrToSomeClass ( new SomeClass() );
      ]
    • Zayıf İşaretçiler [ie
      std::weak_ptr<SomeClass> weakPtrToSomeWeakOrSharedPtr ( weakOrSharedPtr );
      ]

Ham işaretçiler (bazen "eski işaretçiler" veya "C işaretçileri" olarak da adlandırılır) 'çıplak kemikler' işaretçi davranışı sağlar ve yaygın bir hata kaynağı ve bellek sızıntısıdır. Ham işaretçiler kaynağın sahipliğini takip etmek için hiçbir yol sunmaz ve geliştiriciler bellek sızıntısı oluşturmadıklarından emin olmak için manuel olarak 'delete' komutunu çağırmalıdır. Kaynak paylaşılıyorsa bu zorlaşır, çünkü hala herhangi bir nesnenin kaynağa işaret edip etmediğini bilmek zor olabilir. Bu nedenlerden ötürü, ham göstergelerden genellikle kaçınılmalı ve yalnızca kodun sınırlı kapsama sahip performans açısından kritik bölümlerinde kullanılmalıdır.

Benzersiz işaretçiler, kaynağa temel ham işaretçiyi "sahip" olan ve benzersiz işaretçiyi sahibi olan nesne kapsam dışına çıktığında tahsis edilen belleğin silinmesi ve serbest bırakılmasından sorumlu olan temel bir akıllı işaretçidir. 'Benzersiz' adı, belirli bir noktada yalnızca bir nesnenin benzersiz işaretçiye 'sahip olabileceği' anlamına gelir. Sahiplik move komutu ile başka bir nesneye aktarılabilir, ancak benzersiz bir işaretçi asla kopyalanamaz veya paylaşılamaz. Bu nedenlerle, benzersiz işaretçiler, belirli bir zamanda yalnızca bir nesnenin işaretçiye ihtiyaç duyması durumunda ham işaretçiler için iyi bir alternatiftir ve bu, geliştiriciyi, sahip olunan nesnenin yaşam döngüsünün sonunda belleği serbest bırakma ihtiyacından kurtarır.

Paylaşılan işaretçiler, benzersiz işaretlere benzer başka bir akıllı işaretçi türüdür, ancak birçok nesnenin paylaşılan işaretçi üzerinde sahip olmasına izin verir. Benzersiz işaretçi gibi, paylaşılan işaretçiler de tüm nesneler kaynağa işaret ettikten sonra ayrılan belleği boşaltmaktan sorumludur. Bunu referans sayma adı verilen bir teknikle başarır. Her yeni nesne paylaşılan işaretçinin sahipliğini her aldığında, referans sayısı bir artırılır. Benzer şekilde, bir nesne kapsam dışına çıktığında veya kaynağı göstermeyi durdurduğunda, referans sayısı bir azaltılır. Referans sayısı sıfıra ulaştığında, ayrılan bellek serbest bırakılır. Bu nedenlerle, paylaşılan işaretçiler, birden çok nesnenin aynı kaynağa işaret etmesi gerektiğinde kullanılması gereken çok güçlü bir akıllı işaretçi türüdür.

Son olarak, zayıf işaretçiler bir kaynağa doğrudan işaret etmek yerine başka bir işaretçiye (zayıf veya paylaşılan) işaret eden başka bir akıllı işaretçi türüdür. Zayıf işaretçiler bir nesneye doğrudan erişemez, ancak nesnenin hala var olup olmadığını veya süresinin dolup dolmadığını söyleyebilirler. Zayıf bir işaretçi, işaretli nesneye erişmek için geçici olarak paylaşılan bir işaretçiye dönüştürülebilir (hala mevcutsa). Örneklemek için aşağıdaki örneği göz önünde bulundurun:

  • Meşgul ve çakışan toplantılarınız var: Toplantı A ve Toplantı B
  • A Toplantısına gitmeye karar veriyorsunuz ve iş arkadaşınız B Toplantısına gidiyor
  • İş arkadaşınıza, B Toplantısı sona erdikten sonra B Toplantısı devam ediyorsa, katılacağınızı söylersiniz
  • Aşağıdaki iki senaryo oynayabilir:
    • A toplantısı sona erer ve B toplantısı devam eder, dolayısıyla siz
    • A Toplantısı sona erdi ve B Toplantısı da sona erdi, bu yüzden katılamazsınız

Örnekte, Toplantı B'ye zayıf bir işaretçiniz var, Toplantı B'de siz olmadan bitebilmeniz için bir "sahip" değilsiniz ve siz kontrol etmedikçe bitip bitmeyeceğini bilmiyorsunuz. Eğer bitmediyse, katılabilir ve katılabilirsin, aksi halde yapamazsın. Bu, Toplantı B'de paylaşılan bir işaretçiye sahip olmaktan farklıdır, çünkü hem Toplantı A'da hem de Toplantı B'de (her ikisine aynı anda katılırsınız) bir "sahip" olursunuz.

Örnek, zayıf bir işaretçinin nasıl çalıştığını ve bir nesnenin dışarıdaki bir gözlemci olması gerektiğinde yararlı olduğunu gösterir , ancak sahipliği paylaşma sorumluluğunu istemez. Bu özellikle iki nesnenin birbirine işaret etmesi gerektiği senaryoda faydalıdır (dairesel referans olarak da bilinir). Paylaşılan işaretçilerle, her iki nesne de serbest bırakılamaz çünkü diğer nesne tarafından hala 'güçlü bir şekilde' işaret edilirler. İşaretçilerden biri zayıf bir işaretçi olduğunda, zayıf işaretçiyi tutan nesne gerektiğinde diğer nesneye yine de erişebilir.


6

Daha önce bahsedilen diğer geçerli kullanım durumlarının yanı sıra, std::weak_ptrçok iş parçacıklı bir ortamda harika bir araçtır, çünkü

  • Nesneye sahip değildir ve bu nedenle farklı bir iş parçacığında silinmeyi engelleyemez
  • std::shared_ptrile birlikte std::weak_ptrsarkan işaretlere karşı güvenlidir - tersine std::unique_ptrham işaretçilerle birlikte
  • std::weak_ptr::lock()atomik bir işlemdir (ayrıca bkz . poor_ptr'in iş parçacığı güvenliği hakkında )

Bir dizindeki tüm görüntüleri (~ 10.000) aynı anda belleğe yüklemek için bir görev düşünün (örn. Küçük resim önbelleği olarak). Bunu yapmanın en iyi yolu, görüntüleri işleyen ve yöneten bir kontrol iş parçacığı ve görüntüleri yükleyen birden çok çalışan iş parçacığıdır. Şimdi bu kolay bir iş. İşte çok basitleştirilmiş bir uygulama ( join()vb. Atlanmıştır, iş parçacıklarının gerçek bir uygulamada farklı şekilde ele alınması gerekir)

// a simplified class to hold the thumbnail and data
struct ImageData {
  std::string path;
  std::unique_ptr<YourFavoriteImageLibData> image;
};

// a simplified reader fn
void read( std::vector<std::shared_ptr<ImageData>> imagesToLoad ) {
   for( auto& imageData : imagesToLoad )
     imageData->image = YourFavoriteImageLib::load( imageData->path );
}

// a simplified manager
class Manager {
   std::vector<std::shared_ptr<ImageData>> m_imageDatas;
   std::vector<std::unique_ptr<std::thread>> m_threads;
public:
   void load( const std::string& folderPath ) {
      std::vector<std::string> imagePaths = readFolder( folderPath );
      m_imageDatas = createImageDatas( imagePaths );
      const unsigned numThreads = std::thread::hardware_concurrency();
      std::vector<std::vector<std::shared_ptr<ImageData>>> splitDatas = 
        splitImageDatas( m_imageDatas, numThreads );
      for( auto& dataRangeToLoad : splitDatas )
        m_threads.push_back( std::make_unique<std::thread>(read, dataRangeToLoad) );
   }
};

Ancak, görüntülerin yüklenmesini kesmek istiyorsanız, örneğin kullanıcı farklı bir dizin seçtiği için çok daha karmaşık hale gelir. Veya menajeri yok etmek isteseniz bile.

Alanınızı değiştirebilmeniz için önce iplik iletişimine ihtiyacınız vardır ve tüm yükleyici ipliklerini durdurmanız gerekir m_imageDatas. Aksi takdirde yükleyiciler, tüm görüntüler bitene kadar yüklemeye devam eder - zaten kullanılmamış olsalar bile. Basitleştirilmiş örnekte, bu çok zor olmaz, ancak gerçek bir ortamda işler çok daha karmaşık olabilir.

İş parçacıkları muhtemelen, bazıları durdurulup bazıları durdurulmayan birden çok yönetici tarafından kullanılan bir iş parçacığı havuzunun bir parçası olacaktır. Basit parametre imagesToLoad, bu yöneticilerin görüntü isteklerini farklı kontrol iş parçacıklarından zorladığı kilitli bir sıra olacaktır. okuyucular diğer taraftan istekleri - keyfi bir sırayla - patlatırlar. Böylece iletişim zor, yavaş ve hataya açık hale gelir. Bu gibi durumlarda herhangi bir ek iletişimden kaçınmanın çok zarif bir yolu ile std::shared_ptrbirlikte kullanmaktır std::weak_ptr.

// a simplified reader fn
void read( std::vector<std::weak_ptr<ImageData>> imagesToLoad ) {
   for( auto& imageDataWeak : imagesToLoad ) {
     std::shared_ptr<ImageData> imageData = imageDataWeak.lock();
     if( !imageData )
        continue;
     imageData->image = YourFavoriteImageLib::load( imageData->path );
   }
}

// a simplified manager
class Manager {
   std::vector<std::shared_ptr<ImageData>> m_imageDatas;
   std::vector<std::unique_ptr<std::thread>> m_threads;
public:
   void load( const std::string& folderPath ) {
      std::vector<std::string> imagePaths = readFolder( folderPath );
      m_imageDatas = createImageDatas( imagePaths );
      const unsigned numThreads = std::thread::hardware_concurrency();
      std::vector<std::vector<std::weak_ptr<ImageData>>> splitDatas = 
        splitImageDatasToWeak( m_imageDatas, numThreads );
      for( auto& dataRangeToLoad : splitDatas )
        m_threads.push_back( std::make_unique<std::thread>(read, dataRangeToLoad) );
   }
};

Bu uygulama birincisi kadar kolaydır, herhangi bir ek iş parçacığı iletişimi gerektirmez ve gerçek bir uygulamada bir iş parçacığı havuzunun / kuyruğunun bir parçası olabilir. Süresi dolmuş görüntüler atlandığından ve süresi dolmamış görüntüler işlendiğinden, normal çalışma sırasında iş parçacıklarının hiçbir zaman durdurulması gerekmez. Sahibi her zaman güvenli bir şekilde değiştirebilir veya yöneticilerinizi yok edebilirsiniz, çünkü okuyucu işaretçisi süresinin dolup dolmadığını kontrol eder.


2

http://en.cppreference.com/w/cpp/memory/weak_ptr std :: weak_ptr, std :: shared_ptr tarafından yönetilen bir nesneye ait olmayan ("zayıf") bir referansı tutan akıllı bir işaretçidir. Başvurulan nesneye erişmek için std :: shared_ptr biçimine dönüştürülmelidir.

std :: poor_ptr geçici sahiplik modelleri: bir nesneye yalnızca varsa erişilmesi gerektiğinde ve başka bir kişi tarafından herhangi bir zamanda silinebildiğinde, nesneyi izlemek için std :: weak_ptr kullanılır ve std'ye dönüştürülür: : geçici sahiplik sağlamak için shared_ptr. Orijinal std :: shared_ptr şu anda yok edilirse, nesnenin ömrü geçici std :: shared_ptr de yok edilene kadar uzatılır.

Ayrıca, std :: shared_ptr dairesel referanslarını kırmak için std :: poor_ptr kullanılır.


" dairesel referansları kırmak için " nasıl?
curiousguy

2

Paylaşılan işaretçinin bir dezavantajı var: shared_pointer üst-alt döngü bağımlılığını işleyemiyor. Üst sınıf, alt sınıf nesnesini paylaşılan bir işaretçi kullanarak kullanıyorsa, alt sınıf üst sınıf nesnesini kullanıyorsa aynı dosyada anlamına gelir. Paylaşılan işaretçi tüm nesneleri yok edemez, paylaşılan işaretçi bile döngü bağımlılığı senaryosunda yıkıcıyı çağırmaz. temel olarak paylaşılan işaretçi referans sayım mekanizmasını desteklemez.

Bu dezavantaj zayıf_pointer kullanarak üstesinden gelebiliriz.


Zayıf bir referans, dairesel bir bağımlılıkla nasıl başa çıkabilir?
curiousguy

1
@curiousguy, bir çocuk ebeveyne zayıf bir başvuruda bulunur, daha sonra ona işaret eden paylaşılan (güçlü) referanslar olmadığında ebeveyn yeniden yerleştirilebilir. Bu nedenle ebeveyne çocuk aracılığıyla erişirken, zayıf referansın ebeveyin hala mevcut olup olmadığını test etmesi gerekir. Alternatif olarak, bu ekstra koşulu önlemek için, ebeveyne ve çocuğa yalnızca paylaşılan referanslar her birinden olduğunda, dairesel bir referans izleme mekanizması (her ikisi de kötü asimtotik performansa sahip olan işaret süpürme veya yeniden sayım düşüşlerinde problama) dairesel paylaşılan referansları kırabilir. diğer.
Shelby Moore III

@ShelbyMooreIII " ebeveynin hala kullanılabilir olup olmadığını test etmek için " evet ve mevcut olmayan duruma doğru tepki verebilmelisiniz! Bu gerçek (yani güçlü) ref ile gerçekleşmez. Bu, zayıf ref'nin yerine geçme anlamına gelmediği anlamına gelir: mantıkta bir değişiklik gerektirir.
curiousguy

2
@curiousguy, “ weak_ptrProgramın mantığında bir bırakma yedeği olarak değişiklik olmadan dairesel bağımlılığa sahip bir anlaşma nasıl yapılabilir shared_ptr?” :-)
Shelby Moore III

2

Nesneye sahip olmak istemediğimizde:

Ör:

class A
{
    shared_ptr<int> sPtr1;
    weak_ptr<int> wPtr1;
}

Yukarıdaki sınıfta wPtr1, wPtr1 tarafından işaret edilen kaynağa sahip değildir. Kaynak silinirse, wPtr1'in süresi dolar.

Dairesel bağımlılığı önlemek için:

shard_ptr<A> <----| shared_ptr<B> <------
    ^             |          ^          |
    |             |          |          |
    |             |          |          |
    |             |          |          |
    |             |          |          |
class A           |     class B         |
    |             |          |          |
    |             ------------          |
    |                                   |
    -------------------------------------

Şimdi B ve A sınıfının shared_ptr öğesini yaparsak, her iki işaretçinin use_count değeri iki olur.

Shared_ptr kapsam dışına çıktığında sayı hala 1 kalır ve bu nedenle A ve B nesnesi silinmez.

class B;

class A
{
    shared_ptr<B> sP1; // use weak_ptr instead to avoid CD

public:
    A() {  cout << "A()" << endl; }
    ~A() { cout << "~A()" << endl; }

    void setShared(shared_ptr<B>& p)
    {
        sP1 = p;
    }
};

class B
{
    shared_ptr<A> sP1;

public:
    B() {  cout << "B()" << endl; }
    ~B() { cout << "~B()" << endl; }

    void setShared(shared_ptr<A>& p)
    {
        sP1 = p;
    }
};

int main()
{
    shared_ptr<A> aPtr(new A);
    shared_ptr<B> bPtr(new B);

    aPtr->setShared(bPtr);
    bPtr->setShared(aPtr);

    return 0;  
}

çıktı:

A()
B()

Çıkıştan görebildiğimiz gibi A ve B işaretçisinin asla silinmediği ve dolayısıyla bellek sızıntısı olduğu görülüyor.

Böyle bir sorundan kaçınmak için, daha anlamlı olan paylaşılan_ptr yerine A sınıfında zayıf_ptr kullanın.


2

Gördüğüm std::weak_ptr<T>bir şekilde sap a std::shared_ptr<T>Benim ulaşmasını sağlar: std::shared_ptr<T>hala varsa, ancak onun ömrünü uzatmak olmaz. Böyle bir bakış açısının yararlı olduğu birkaç senaryo vardır:

// Some sort of image; very expensive to create.
std::shared_ptr< Texture > texture;

// A Widget should be able to quickly get a handle to a Texture. On the
// other hand, I don't want to keep Textures around just because a widget
// may need it.

struct Widget {
    std::weak_ptr< Texture > texture_handle;
    void render() {
        if (auto texture = texture_handle.get(); texture) {
            // do stuff with texture. Warning: `texture`
            // is now extending the lifetime because it
            // is a std::shared_ptr< Texture >.
        } else {
            // gracefully degrade; there's no texture.
        }
    }
};

Bir başka önemli senaryo veri yapılarındaki döngüleri kırmaktır.

// Asking for trouble because a node owns the next node, and the next node owns
// the previous node: memory leak; no destructors automatically called.
struct Node {
    std::shared_ptr< Node > next;
    std::shared_ptr< Node > prev;
};

// Asking for trouble because a parent owns its children and children own their
// parents: memory leak; no destructors automatically called.
struct Node {
    std::shared_ptr< Node > parent;
    std::shared_ptr< Node > left_child;
    std::shared_ptr< Node > right_child;
};

// Better: break dependencies using a std::weak_ptr (but not best way to do it;
// see Herb Sutter's talk).
struct Node {
    std::shared_ptr< Node > next;
    std::weak_ptr< Node > prev;
};

// Better: break dependencies using a std::weak_ptr (but not best way to do it;
// see Herb Sutter's talk).
struct Node {
    std::weak_ptr< Node > parent;
    std::shared_ptr< Node > left_child;
    std::shared_ptr< Node > right_child;
};

Herb Sutter, Varsayılan olarak Sızıntı Özgürlüğü sağlamak için dil özelliklerinin (bu durumda akıllı işaretçiler) en iyi kullanımını açıklayan mükemmel bir konuşmaya sahiptir (yani: her şey inşaat yoluyla yerine oturur; zorlukla vidalayabilirsiniz). Bu bir zorunluluktur izle.

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.