C ++ 11'in akıllı göstergelerini incelemeye başladım ve herhangi bir yararlı kullanımı görmüyorum std::weak_ptr
. Birisi bana ne zaman std::weak_ptr
yararlı / gerekli olduğunu söyleyebilir mi?
C ++ 11'in akıllı göstergelerini incelemeye başladım ve herhangi bir yararlı kullanımı görmüyorum std::weak_ptr
. Birisi bana ne zaman std::weak_ptr
yararlı / gerekli olduğunu söyleyebilir mi?
Yanıtlar:
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.
std::weak_ptr
sarkan 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_ptr
verileri yönetmesine izin vererek ve std::weak_ptr
kullanıcılara veri sağlayarak , kullanıcılar expired()
veya öğelerini arayarak verilerin geçerliliğini denetleyebilir lock()
.
std::shared_ptr
Tü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";
}
std::weak_ptr::lock
std::shared_ptr
yönetilen nesnenin sahipliğini paylaşan yeni bir öğe oluşturur .
Başka bir cevap, umarım daha basittir. (diğer Google çalışanları için)
Varsayalım Team
veMember
nesneleriniz var.
Açıkçası bu bir ilişkidir: Team
nesnenin 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_ptr
ve "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
shared_ptr
mü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_ptr
başlamak için a kullanmaya gerek yoktur .
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.
İş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.
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_ptr
kullanarak 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.
this
self = shared_from_this()
İşleyici aynı sınıf içindeki yöntemleri çağırdığında deyimi kullanırken alışkanlık gücü .
shared_ptr : gerçek nesneyi tutar.
poor_ptr : lock
gerçek sahibe bağlanmak için kullanılır veya shared_ptr
aksi takdirde NULL döndürür.
Kabaca söylemek gerekirse, weak_ptr
rol 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 .
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());
İş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:
SomeClass* ptrToSomeClass = new SomeClass();
]std::unique_ptr<SomeClass> uniquePtrToSomeClass ( new SomeClass() );
std::shared_ptr<SomeClass> sharedPtrToSomeClass ( new SomeClass() );
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:
Ö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.
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ü
std::shared_ptr
ile birlikte std::weak_ptr
sarkan işaretlere karşı güvenlidir - tersine std::unique_ptr
ham işaretçilerle birliktestd::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_ptr
birlikte 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.
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.
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.
weak_ptr
Programı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
?” :-)
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.
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.