2015-02-05 16 views
7
#include <memory> 
#include <iostream> 

struct A : public std::enable_shared_from_this<A> 
{ 
    ~A() 
    { 
     auto this_ptr = shared_from_this(); // std::bad_weak_ptr exception here. 
     std::cout << "this: " << this_ptr; 
    } 
}; 

int main() 
{ 
    auto a = std::make_shared<A>(); 
    a.reset(); 
    return 0; 
} 

Ich bekomme std::bad_weak_ptr Ausnahme beim Aufruf shared_from_this(). Ist es von Entwurf? Ja, es könnte gefährlich sein, da dieser Zeiger nicht verwendet werden kann, nachdem der Destruktor zurückkehrt, aber ich sehe keinen Grund, warum es technisch unmöglich wäre, den Zeiger hier zu bekommen, da das geteilte Zeigerobjekt offensichtlich noch existiert und sein kann benutzt. Gibt es eine Möglichkeit, dies zu umgehen, kurz, meine eigene enable_shared_from_this Analog zu schreiben (was ich lieber nicht tun würde)?std :: enable_shared_from_this: Ist es erlaubt, shared_from_this() im Destruktor aufzurufen?

+0

http://StackOverflow.com/Q/8501503/1147772 – Drax

+0

@Drax: Ich habe diese Frage gesehen. Es betrifft "boost" und nicht "std", und die Antworten sprechen über das spezifische Design des fraglichen Codes und nicht über prinzipielle Einschränkungen für die Verfügbarkeit von "shared_from_this()". –

+0

@VioletGiraffe Die Frage betrifft weder 'boost' noch' std', sondern nur das Konzept der schwachen Referenz. – curiousguy

Antwort

6

Ich sehe keinen Grund, warum es technisch unmöglich wäre, den Zeiger hier zu bekommen, da das Shared-Pointer-Objekt offensichtlich noch existiert und verwendet werden kann.

Es gibt einen sehr guten technischen Grund, warum es nicht möglich ist.

Die shared_ptr möglicherweise vorhanden, aber die Verweisanzahl für das Objekt A hat Null erreicht, deshalb wird der Destruktor ausgeführt. Sobald der Referenzzähler null erreicht hat, kann er nicht wieder erhöht werden (andernfalls könnten Sie einen erhalten, der auf ein Objekt verweist, das entweder mitten in seinem Destruktor ist oder bereits zerstört wurde).

Der Aufruf shared_from_this() versucht, die Referenzanzahl zu erhöhen und eine shared_ptr zurückzugeben, die das Eigentum mit den aktuellen Besitzern teilt, aber Sie können den Zähler nicht von null auf eins erhöhen.

In diesem sehr speziellen Fall (innerhalb des destructor des Objekts) wissen Sie das Objekt noch nicht vollständig zerstört worden, aber enable_shared_from_this<A> hat keine Möglichkeit, zu wissen, wer die shared_from_this() Funktion aufruft, so kann nicht wissen, ob es passiert in dieser sehr spezielle Fall oder in einem anderen Stück Code außerhalb des Destruktors des Objekts (zB in einem anderen Thread, der nach dem Destruktor weitergehen wird).

Wenn Sie es irgendwie für diesen speziellen Fall arbeiten könnten und Sie eine shared_ptr<A>, die sich auf das Objekt, das gerade zerstört wird, beziehen, könnten Sie das shared_ptr zu etwas außerhalb des Destruktor geben, die es für die spätere Verwendung gespeichert. Das würde ermöglichen, dass ein anderer Code auf einen baumelnden shared_ptr zugreift, nachdem das Objekt zerstört wurde. Das wäre ein großes Loch im shared_ptr und weak_ptr Typ System.

+0

Große Erklärung, danke. –

0

shared_ptr::reset10 Implementierung ist oft shared_ptr().swap(*this).

Das bedeutet, dass die shared_ptr, die Sie versuchen zu kopieren, bereits in seinem Destruktorstatus ist, der wiederum die gemeinsame Zählung dekrementiert, bevor Ihr Destruktor aufgerufen wird. Wenn Sie enable_shared_from_this aufrufen wird es versuchen, die weak_ptr darin gespeichert zu fördern, indem sie eine shared_ptr von diesem weak_ptr konstruieren, die zu einer Ausnahme führt, wenn die Zählung 0 ist

So Ihre Frage zu beantworten, gibt es keine Standardmethode zu tun, was Sie möchten, dass sich Ihre Standardbibliotheksimplementierung nicht so verhält, dass sie autorisiert wird (ich weiß nicht, ob es vom Standard vorgeschrieben ist oder nicht).

Hier ist ein Hack, der auf meinem Rechner (Klirren/libC++) funktioniert:

#include <memory> 
#include <iostream> 

class hack_tag 
{ 
}; 

namespace std 
{ 

    template<> 
    class shared_ptr<hack_tag> 
    { 
    public: 
    template<typename T> 
    weak_ptr<T>  extract_weak(const enable_shared_from_this<T>& shared) 
    { 
     return shared.__weak_this_; 
    } 
    }; 

}; 

using weak_ptr_extractor = std::shared_ptr<hack_tag>; 

class test : public std::enable_shared_from_this<test> 
{ 
public: 
    test() 
    { 
    std::cout << "ctor" << std::endl; 
    } 

    ~test() 
    { 
    std::cout << "dtor" << std::endl; 
    weak_ptr_extractor hacker; 
    auto weak = hacker.extract_weak(*this); 
    std::cout << weak.use_count() << std::endl; 
    auto shared = weak.lock(); 
    } 
}; 


int  main(void) 
{ 
    std::shared_ptr<test> ptr = std::make_shared<test>(); 

    ptr.reset(); 
} 

Aber ich bin nicht sicher, dass Sie mit, dass nützliches alles tun können, da Ihr Besitz shared_ptr, die Sie kopieren zu sterben und diese Kopie teilt nicht Dinge mit dem neuen sauberen shared_ptr Sie nach dem reset Anruf erhalten.

9

[util.smartptr.enab]/7 beschreibt die Voraussetzungen für shared_from_this:

Benötigt:enable_shared_from_this<T> ist eine leicht zugängliche Basisklasse von T sein. *this soll ein Unterobjekt eines Objekts t vom Typ T sein. Es muss mindestens eine shared_ptr Instanz p sein, die &t besitzt. [emph. hinzugefügt]

Da Ihr Objekt zerstört wird, muss es sein, dass es keine shared_ptr besitzt. Folglich können Sie shared_from_this nicht aufrufen, ohne diese Anforderung zu verletzen, was zu undefiniertem Verhalten führt.

+1

"Da Ihr Objekt zerstört wird, muss es sein, dass es kein' shared_ptr' gibt, das es besitzt. "Es sei denn, jemand ist verrückt genug, um einen expliziten Destruktor-Aufruf auszuführen;) –

+1

@ T.C. Wenn das der Fall ist, dann denkt "jemand" offensichtlich, dass * sie * tatsächlich der Eigentümer sind und nicht das 'shared_ptr', das sich auf das Objekt bezieht. Mein Argument steht;) – Casey

+0

Mein Gedanke war, dass, da 'shared_ptr' für die Zerstörung eines Objekts verantwortlich ist, der Zeiger nicht zerstört wird, bis _after_ das Objekt, das er enthält, gelöscht wird. Aber anscheinend wird das Referenzzählerobjekt zumindest in der MS-Implementierung vorher zerstört, so dass es im Destruktor bereits nicht verfügbar ist. –