"Enable_shared_from_this" in faydası nedir?


349

Ben karşılaştım enable_shared_from_thisBoost.Asio örnekler okurken ve hala bu doğru nasıl kullanılması gerektiği için kayıp belgeleri okuduktan sonra. Birisi bana bir örnek verebilir mi ve bu sınıfı kullanırken bir açıklama mantıklı olabilir.

Yanıtlar:


362

Sahip olduğunuz her şey için geçerli bir shared_ptrörnek thisalmanızı sağlar this. O olmadan, bir almanın yolu yoktur shared_ptriçin thiszaten bir üye olarak bir vardı sürece. Enable_shared_from_this için boost belgelerinden bu örnek :

class Y: public enable_shared_from_this<Y>
{
public:

    shared_ptr<Y> f()
    {
        return shared_from_this();
    }
}

int main()
{
    shared_ptr<Y> p(new Y);
    shared_ptr<Y> q = p->f();
    assert(p == q);
    assert(!(p < q || q < p)); // p and q must share ownership
}

Üye örneği olmasa bile yöntem f()geçerli bir değer döndürür shared_ptr. Bunu yapamayacağınızı unutmayın:

class Y: public enable_shared_from_this<Y>
{
public:

    shared_ptr<Y> f()
    {
        return shared_ptr<Y>(this);
    }
}

Bunun döndürdüğü paylaşılan işaretçinin "uygun" olandan farklı bir referans sayısı olacaktır ve bunlardan biri nesne silindiğinde sarkan bir referansı kaybedip tutacaktır.

enable_shared_from_thisC ++ 11 standardının bir parçası haline gelmiştir. Ayrıca buradan ve takviyeden de alabilirsiniz.


202
+1. Kilit nokta, sadece shared_ptr <Y> (this) döndürmenin "bariz" tekniğinin kırılmasıdır, çünkü bu, ayrı referans sayılarıyla birden çok farklı shared_ptr nesnesi oluşturmaya başlar. Bu nedenle, asla aynı ham işaretçiden birden fazla shared_ptr oluşturmamalısınız .
j_random_hacker

3
O içinde olduğunu belirtmek gerekir 11 ve üstü C ++ , öyle tamamen geçerli bir kullanımı std::shared_ptrbir üzerinde yapıcı ham pointer eğer o devralır std::enable_shared_from_this. Boost'un anlambiliminin bunu destekleyecek şekilde güncellenip güncellenmediğini bilmiyorum .
Matthew

6
@MatthewHolder Bunun için bir teklifiniz var mı? Cppreference.com üzerinde " std::shared_ptrZaten başka bir tarafından yönetilen bir nesne için bir yapı oluşturmak std::shared_ptr, dahili olarak saklanan zayıf başvuru danışmaz ve böylece tanımlanamayan davranış yol açacaktır." ( en.cppreference.com/w/cpp/memory/enable_shared_from_this )
Thorbjørn Lindeijer

5
Neden sadece yapamıyorsun shared_ptr<Y> q = p?
Dan M.

2
@ ThorbjørnLindeijer, haklısın, bu C ++ 17 ve üstü. Bazı uygulamalar, yayınlanmadan önce C ++ 16 semantiğini izledi. C ++ 11 ila C ++ 14 için uygun kullanım kullanılmalıdır std::make_shared<T>.
Matthew

198

Zayıf işaretçiler hakkındaki Dr Dobbs makalesinden, bu örneğin anlaşılması daha kolay olduğunu düşünüyorum (kaynak: http://drdobbs.com/cpp/184402026 ):

... bunun gibi kodlar düzgün çalışmaz:

int *ip = new int;
shared_ptr<int> sp1(ip);
shared_ptr<int> sp2(ip);

İki shared_ptrnesneden hiçbiri diğerini bilmez, bu yüzden her ikisi de yok edildiğinde kaynağı serbest bırakmaya çalışır. Bu genellikle sorunlara yol açar.

Benzer şekilde, bir üye işlevi shared_ptrçağrıldığı nesnenin sahibi olan bir nesneye ihtiyaç duyuyorsa, yalnızca anında bir nesne oluşturamaz:

struct S
{
  shared_ptr<S> dangerous()
  {
     return shared_ptr<S>(this);   // don't do this!
  }
};

int main()
{
   shared_ptr<S> sp1(new S);
   shared_ptr<S> sp2 = sp1->dangerous();
   return 0;
}

Bu kod, daha ince bir formda olmasına rağmen, önceki örnekle aynı soruna sahiptir. İnşa edildiğinde, shared_ptr nesnesi sp1yeni tahsis edilen kaynağın sahibidir. Üye işlevinin içindeki kod S::dangerouso shared_ptrnesneyi bilmez , bu nedenle shared_ptrdöndürdüğü nesne birbirinden farklıdır sp1. Yeni shared_ptrnesneyi kopyalamak sp2yardımcı olmaz; ne zaman sp2kapsam dışına gider, bu kaynağı serbest ve ne zaman olacak sp1kapsam dışına gider, yine kaynak yayınlayacak.

Bu sorunu önlemenin yolu sınıf şablonunu kullanmaktır enable_shared_from_this. Şablon, yönetilen kaynağı tanımlayan sınıfın adı olan bir şablon türü bağımsız değişkeni alır. Bu sınıf, sırayla, şablondan alenen türetilmelidir; bunun gibi:

struct S : enable_shared_from_this<S>
{
  shared_ptr<S> not_dangerous()
  {
    return shared_from_this();
  }
};

int main()
{
   shared_ptr<S> sp1(new S);
   shared_ptr<S> sp2 = sp1->not_dangerous();
   return 0;
}

Bunu yaptığınızda, çağırdığınız nesnenin shared_from_thisbir shared_ptrnesneye ait olması gerektiğini unutmayın . Bu işe yaramaz:

int main()
{
   S *p = new S;
   shared_ptr<S> sp2 = p->not_dangerous();     // don't do this
}

15
Teşekkürler, bu sorunun şu anda kabul edilen cevaptan daha iyi çözüldüğünü göstermektedir.
goertzenator

2
+1: Güzel cevap. Bir yana, bunun yerine shared_ptr<S> sp1(new S);kullanılması tercih edilebilir shared_ptr<S> sp1 = make_shared<S>();, örneğin bkz. Stackoverflow.com/questions/18301511/…
Arun

4
Eminim son satır okumak gerekir shared_ptr<S> sp2 = p->not_dangerous();çünkü burada tuzak ilk kez aramadan önce normal şekilde bir shared_ptr oluşturmak gerekir shared_from_this()! Bu yanlış yapmak gerçekten kolay! C ++ 17 de önce olan UB çağırmak shared_from_this(): tam olarak bir Shared_ptr Normal şekilde yaratılmıştır önce auto sptr = std::make_shared<S>();veya shared_ptr<S> sptr(new S());. Neyse ki C ++ 17'den itibaren bunu yapacağız.
AnorZaken


2
@AnorZaken İyi bir nokta. Bu düzeltmeyi yapmak için bir düzenleme isteği göndermiş olsaydınız faydalı olurdu. Sadece yaptım. Diğer faydalı şey, posterin öznel, bağlama duyarlı yöntem adlarını seçmemesidir!
underscore_d

30

İşte benim açıklamam, somun ve cıvata perspektifinden (en iyi cevap benimle 'tıklamadı'). * Bunun Visual Studio 2012 ile birlikte gelen shared_ptr ve enable_shared_from_this kaynağının araştırılmasının sonucu olduğunu unutmayın. Belki de diğer derleyiciler enable_shared_from_this'i farklı şekilde uygular ... *

enable_shared_from_this<T>weak_ptr<T>örneğinin T' bir gerçek referans sayısını ' içeren özel bir örnek ekler T.

Böylece, shared_ptr<T>yeni bir T * üzerine ilk oluşturduğunuzda , T * 'nin iç zayıf_ptr değeri 1 değerinde bir yeniden sayımla başlatılır. Yeni shared_ptrtemelde buna dayanır weak_ptr.

Tdaha sonra, metodlarını, arayabilir shared_from_thisbir örneği elde etmek için shared_ptr<T>bu aynı iç saklanan referans sayısı üzerine sırt . Bu şekilde, birbirlerini bilmeyen T*birden çok shared_ptrörneğe sahip olmak yerine her zaman ref sayımının depolandığı tek bir yeriniz olur ve her biri shared_ptr, ref sayımından Tve ref sayımından silmekten sorumlu olduklarını düşünür. -count sıfıra ulaşır.


1
Bu doğrudur ve gerçekten önemli olan kısım, So, when you first create...bunun bir gereklilik olmasıdır (zayıf_ptr dediğiniz gibi, nesne işaretçisini bir paylaşılan_ptr ctor'a geçirene kadar başlatılmaz!) Ve bu gereksinim, dikkatsiz. Aramadan önce shared_from_thisbir paylaşılan_ptr oluşturmazsanız UB alırsınız - aynı şekilde birden fazla paylaşılan_ptr oluşturursanız UB de alırsınız. Bir şekilde tam olarak bir kez shared_ptr oluşturduğunuzdan emin olmalısınız .
AnorZaken

2
Diğer bir deyişle bütün fikir enable_shared_from_thisnoktası elde edebilmek için olduğundan ile başlayacak kırılgandır shared_ptr<T>bir mesafede T*, ama gerçekte bir işaretçi olsun T* to önceden paylaşılan veya olmasın olmak hakkında bir şey varsaymak genellikle güvenli değildir ve yanlış tahminde bulunmak UB.
AnorZaken

" İç zayıf_ptr, 1 yeniden sayımla başlatılır " Zayıf ptr, T için akıllı ptr'ye sahip değildir. Zayıf bir ptr, diğer sahip olunan ptr'in " kopyası " olan bir sahiplik ptr'si yapmak için yeterli bilgiye sahip akıllı bir ref. Zayıf bir ptr'nin ref sayımı yoktur. Tüm ref sahiplerine sahip olduğu gibi bir ref sayısına erişebilir.
curiousguy

3

Bir boost :: intrusive_ptr kullanmanın bu sorundan muzdarip olmadığını unutmayın. Bu genellikle bu sorunu aşmanın daha kolay bir yoludur.


Evet, ancak enable_shared_from_thisözellikle kabul eden bir API ile çalışmanıza olanak tanır shared_ptr<>. Benim düşünceme göre, böyle bir API genellikle Yanlış Yapıyor (yığındaki daha yüksek bir şeyin belleğe sahip olmasına izin vermek daha iyi olduğu için), ancak böyle bir API ile çalışmak zorunda kalırsanız, bu iyi bir seçenektir.
cdunn2001

2
Mümkün olduğunca standart içinde kalmak daha iyi.
Sergei

3

C ++ 11 ve sonraki sürümlerde tamamen aynıdır: Size ham bir işaretçi verdiği thisiçin paylaşılan bir işaretçi olarak geri dönme özelliğini etkinleştirmektir this.

başka bir deyişle, kodu böyle çevirmenize izin verir

class Node {
public:
    Node* getParent const() {
        if (m_parent) {
            return m_parent;
        } else {
            return this;
        }
    }

private:

    Node * m_parent = nullptr;
};           

bunun içine:

class Node : std::enable_shared_from_this<Node> {
public:
    std::shared_ptr<Node> getParent const() {
        std::shared_ptr<Node> parent = m_parent.lock();
        if (parent) {
            return parent;
        } else {
            return shared_from_this();
        }
    }

private:

    std::weak_ptr<Node> m_parent;
};           

Bu, yalnızca bu nesneler her zaman bir tarafından yönetiliyorsa çalışır shared_ptr. Durumun doğru olduğundan emin olmak için arayüzü değiştirmek isteyebilirsiniz.
curiousguy

1
Kesinlikle doğru @curiousguy. Bu söylemeye gerek yok. Ayrıca genel API'larımı tanımlarken okunabilirliği artırmak için paylaşılan_ptr'imin tümünü tanımlamayı seviyorum. Örneğin, bunun yerine, std::shared_ptr<Node> getParent const()normalde NodePtr getParent const()bunun yerine ortaya koyarım. Kesinlikle dahili ham işaretçiye erişmeniz gerekiyorsa (en iyi örnek: bir C kütüphanesi ile uğraşmak), bunun std::shared_ptr<T>::getiçin bahsetmekten nefret ediyorum, çünkü bu ham işaretçi erişimcisinin yanlış nedenle çok fazla kullandım.
mchiasson

-3

Diğer bir yolu bir eklemektir weak_ptr<Y> m_stubiçine üyesi class Y. Sonra yaz:

shared_ptr<Y> Y::f()
{
    return m_stub.lock();
}

Türettiğiniz sınıfı değiştiremediğinizde kullanışlıdır (örneğin, başkalarının kitaplığını genişletme). Üyeyi başlatmayı unutmayın, örneğin m_stub = shared_ptr<Y>(this), bir kurucu sırasında bile geçerlidir.

Kalıtım hiyerarşisinde bunun gibi daha fazla taslak varsa, nesnenin yok edilmesini engellemez.

Düzenleme: Doğru kullanıcı nobar tarafından işaret gibi, atama bittiğinde ve geçici değişkenler yok edildiğinde kod Y nesnesini yok. Bu nedenle cevabım yanlış.


4
Buradaki shared_ptr<>amacınız pointee silmeyen bir tane üretmekse, bu aşırıya kaçmaktır. Hiçbir şey almayan ve yapmayan tek işlevli bir nesnenin return shared_ptr<Y>(this, no_op_deleter);nerede no_op_deleterolduğunu söyleyebilirsiniz Y*.
John Zwinck

2
Bunun çalışan bir çözüm olması pek olası görünmüyor. m_stub = shared_ptr<Y>(this)bundan geçici bir shared_ptr oluşturacak ve derhal imha edecektir. Bu ifade sona erdiğinde, thissilinecek ve sonraki tüm referanslar sarkacak.
nobar

2
Yazar, bu cevabın yanlış olduğunu ve muhtemelen silebileceğini kabul ediyor. Ama son 4.5 yılda giriş yaptı, bu yüzden bunu yapmak olası değil - daha yüksek güce sahip biri bu kırmızı ringa balığı çıkarabilir mi?
Tom Goodfellow

Eğer uygulanmasına bakarsanız, enable_shared_from_thisbir weak_ptr(kendisi tarafından doldurulan) kendisini tutar , shared_ptraradığınızda olarak döndürülür shared_from_this. Başka bir deyişle, enable_shared_from_thiszaten sağlayanı çoğaltıyorsunuz .
mchiasson
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.