2016-05-31 16 views
5

Ich habe noch nicht den folgenden Weg gefunden, zyklische Referenzen zu brechen, die in irgendeinem großen C++ Forum/Blog erklärt werden, wie auf GotW, also wollte ich fragen, ob die Technik bekannt ist, und was sind ihre Vor- und Nachteile?Breaking zyklische Referenzen mit std :: weak_ptr und Alias-Konstruktor: Sound oder problematisch?

class Node : public std::enable_shared_from_this<Node> { 
public: 
    std::shared_ptr<Node> getParent() { 
     return parent.lock();  
    } 

    // the getter functions ensure that "parent" always stays alive! 
    std::shared_ptr<Node> getLeft() { 
     return std::shared_ptr<Node>(shared_from_this(), left.get()); 
    } 

    std::shared_ptr<Node> getRight() { 
     return std::shared_ptr<Node>(shared_from_this(), right.get()); 
    } 

    // add children.. never let them out except by the getter functions! 
public: 
    std::shared_ptr<Node> getOrCreateLeft() { 
     if(auto p = getLeft()) 
      return p; 
     left = std::make_shared<Node>(); 
     left->parent = shared_from_this(); 
     return getLeft(); 
    } 

    std::shared_ptr<Node> getOrCreateRight() { 
     if(auto p = getRight()) 
      return p; 
     right = std::make_shared<Node>(); 
     right->parent = shared_from_this(); 
     return getRight(); 
    } 

private: 
    std::weak_ptr<Node> parent; 
    std::shared_ptr<Node> left; 
    std::shared_ptr<Node> right; 
}; 

Von außen bemerkt der Benutzer von Node nicht den Trick mit den Aliasing-Konstruktor in getLeft mit und getRight, aber dennoch kann der Benutzer sicher sein, dass getParent immer einen nicht leeren freigegebenen Zeiger zurückgibt, weil alle Zeiger, die von p->get{Left,Right} zurückgegeben werden, behalten das Objekt *p für die Lebensdauer des zurückgegebenen untergeordneten Zeigers am Leben.

Werde ich hier etwas übersehen, oder ist dies ein offensichtlicher Weg, zyklische Referenzen zu brechen, die bereits dokumentiert wurden?

int main() { 
    auto n = std::make_shared<Node>(); 
    auto c = n->getOrCreateLeft(); 
    // c->getParent will always return non-null even if n is reset()! 
} 
+0

Bedeutet dies, dass letztendlich * alle Kinder * die gleiche Referenzzahl wie die Wurzel haben? Das heißt, wenn ich 'left_A' mit dem Aliasing-Konstruktor erstelle, ist der Referenzzähler derselbe wie 'Eltern'. Wenn ich dann ein neues 'left_B' off' left_A' erzeuge und 'left_A-> shared_from_this' verwende - ist die Referenzzählung immer noch' parent', da in der Indirektion der Baum weiterläuft? In diesem Fall teilen sich alle Knoten die gleiche Referenzanzahl und Sie können niemals einen Knoten entfernen und seine Ressourcen recyceln, bis die gesamte Struktur entfernt wurde. –

+0

@SteveLorimer teilen sie die Referenzzahl der Wurzel nicht, es sei denn, sie wurden der Außenwelt durch eine der "Get" -Funktionen eines Knotens gegeben (die wiederum von einem anderen Knoten vergeben worden wären, und so weiter, beginnend bei der Ursprung). Wenn also nicht jemand einen Verweis auf einen Kindknoten hat (was vielleicht nur temporär der Fall ist, wenn der Baum während einer asynchronen Operation läuft), wird die Referenzzählung nicht geteilt und Ressourcen können freigegeben werden. –

Antwort

3

The shared_ptr<Node> von Ihrem getParent zurück besitzt die Eltern, nicht die Eltern der Eltern.

Auf diese Weise shared_ptr wieder getParent aufrufen kann eine leere (und null) shared_ptr zurückgeben. Zum Beispiel:

int main() { 
    auto gp = std::make_shared<Node>(); 
    auto p = gp->getOrCreateLeft(); 
    auto c = p->getOrCreateLeft(); 
    gp.reset(); 
    p.reset(); // grandparent is dead at this point 
    assert(c->getParent()); 
    assert(!c->getParent()->getParent()); 
} 

(Die geerbt shared_from_this geht aus auch shared_ptr s, die den Knoten anstatt seine Eltern besitzen, aber ich nehme an, Sie es schwieriger zu vermasseln von einer privaten mit Erklärung und es verbieten vertraglich zu machen.)

+0

Ah, das ist schlecht, guter Punkt! –

+0

Ich denke, es funktioniert, wenn Sie einen schwachen Zeiger auf das Eltern- und das Stammverzeichnis speichern und in den Get-Funktionen den Stammzeiger an den Aliaskonstruktor übergeben, richtig? –

+0

Solange Sie die untergeordneten Knoten nicht erneut löschen möchten. Das würde zu einer Katastrophe führen –