std :: shared_ptr davon

100

Ich versuche gerade zu lernen, wie man intelligente Zeiger verwendet. Bei einigen Experimenten entdeckte ich jedoch die folgende Situation, für die ich keine zufriedenstellende Lösung finden konnte:

Stellen Sie sich vor, Sie haben ein Objekt der Klasse A als Eltern eines Objekts der Klasse B (das Kind), aber beide sollten sich kennen:

class A;
class B;

class A
{
public:
    void addChild(std::shared_ptr<B> child)
    {
        children->push_back(child);

        // How to do pass the pointer correctly?
        // child->setParent(this);  // wrong
        //                  ^^^^
    }

private:        
    std::list<std::shared_ptr<B>> children;
};

class B
{
public:
    setParent(std::shared_ptr<A> parent)
    {
        this->parent = parent;
    };

private:
    std::shared_ptr<A> parent;
};

Die Frage ist, wie ein Objekt der Klasse A ein std::shared_ptrvon sich selbst ( this) an sein Kind weitergeben kann.

Es gibt Lösungen für gemeinsam genutzte Boost-Zeiger ( Getting a boost::shared_ptrforthis ), aber wie geht man mit den std::intelligenten Zeigern damit um?

Ikarus
quelle
2
Wie bei jedem anderen Tool müssen Sie es verwenden, wenn es angemessen ist. Verwenden von intelligenten Zeigern für das, was Sie tun, ist nicht
YePhIcK
Ähnlich zu steigern. Siehe hier .
Juanchopanza
1
Dies ist ein Problem auf dieser Abstraktionsebene. Sie wissen nicht einmal, dass "dies" auf den Speicher auf dem Heap verweist.
Vaughn Cato
Nun, die Sprache nicht, aber Sie tun es. Solange Sie verfolgen, was wo ist, wird es Ihnen gut gehen.
Alex

Antworten:

167

Es gibt std::enable_shared_from_thisnur zu diesem Zweck. Sie erben davon und können .shared_from_this()innerhalb der Klasse aufrufen . Außerdem erstellen Sie hier zirkuläre Abhängigkeiten, die zu Ressourcenlecks führen können. Das kann mit der Verwendung von gelöst werden std::weak_ptr. Ihr Code könnte also so aussehen (vorausgesetzt, Kinder verlassen sich auf die Existenz eines Elternteils und nicht umgekehrt):

class A;
class B;

class A
    : public std::enable_shared_from_this<A>
{
public:
    void addChild(std::shared_ptr<B> child)
    {
        children.push_back(child);

        // like this
        child->setParent(shared_from_this());  // ok
        //               ^^^^^^^^^^^^^^^^^^
    }

private:     
    // note weak_ptr   
    std::list<std::weak_ptr<B>> children;
    //             ^^^^^^^^
};

class B
{
public:
    void setParent(std::shared_ptr<A> parent)
    {
        this->parent = parent;
    }

private:
    std::shared_ptr<A> parent;
};

Beachten Sie jedoch, dass für Anrufe .shared_from_this()erforderlich thisist , dass sie dem Anrufpunkt gehören std::shared_ptr. Dies bedeutet, dass Sie ein solches Objekt nicht mehr auf einem Stapel erstellen können und im Allgemeinen nicht mehr .shared_from_this()innerhalb eines Konstruktors oder Destruktors aufrufen können .

Yuri Kilochek
quelle
1
Vielen Dank für Ihre Erklärung und für den Hinweis auf mein zirkuläres Abhängigkeitsproblem.
Ikarus
@Deduplicator was meinst du?
Yuri Kilochek
Versuchen Sie, eine shared_ptrbasierend auf einer Standardkonstruktion zu erstellen shared_ptrund auf was auch immer Sie zeigen möchten ...
Deduplicator
1
@Deduplicator das ist ein, verzeihen Sie mein Wortspiel, ziemlich sinnloser gemeinsamer Zeiger. Dieser Konstruktor soll mit Zeigern auf Mitglieder des verwalteten Objekts oder seiner Basen verwendet werden. Auf jeden Fall, was ist dein Punkt (es tut mir leid)? Diese nicht besitzenden shared_ptrs sind für diese Frage irrelevant. shared_from_thisDie Voraussetzungen besagen eindeutig, dass das Objekt shared_ptrzum Zeitpunkt des Anrufs einigen gehören muss (nicht nur darauf hingewiesen wird) .
Yuri Kilochek
1
@kazarey Das Eigentum von a shared_ptrist zum Zeitpunkt des Anrufs erforderlich shared_ptr<Foo> p(new Foo());, shared_ptrübernimmt jedoch in einem typischen Verwendungsmuster, dh so etwas wie , das Eigentum an dem Objekt erst, nachdem es vollständig erstellt wurde. Es ist möglich, dies zu umgehen, indem Sie einen shared_ptrKonstruktor erstellen, der mit initialisiert wurde, thisund ihn an einem nicht lokalen Ort speichern (z. B. in einem Referenzargument), damit er nicht stirbt, wenn der Konstruktor abgeschlossen ist. Es ist jedoch unwahrscheinlich, dass dieses verschlungene Szenario notwendig ist.
Yuri Kilochek
9

Sie haben mehrere Probleme in Ihrem Design, die auf Ihr Missverständnis von intelligenten Zeigern zurückzuführen zu sein scheinen.

Intelligente Zeiger werden verwendet, um den Besitz zu deklarieren. Sie brechen dies, indem Sie erklären, dass beide Elternteile alle Kinder besitzen, aber auch, dass jedes Kind seine Eltern besitzt. Beides kann nicht wahr sein.

Außerdem geben Sie einen schwachen Zeiger zurück getChild(). Auf diese Weise erklären Sie, dass sich der Anrufer nicht um das Eigentum kümmern sollte. Dies kann sehr einschränkend sein, aber Sie müssen auch sicherstellen, dass das betreffende Kind nicht zerstört wird, solange noch schwache Zeiger vorhanden sind. Wenn Sie einen intelligenten Zeiger verwenden, wird dieser von selbst aussortiert .

Und das Letzte. Wenn Sie neue Entitäten akzeptieren, sollten Sie normalerweise Rohzeiger akzeptieren. Intelligente Zeiger können ihre eigene Bedeutung für den Austausch von Kindern zwischen Eltern haben. Für den allgemeinen Gebrauch sollten Sie jedoch rohe Zeiger akzeptieren.

Šimon Tóth
quelle
Sieht so aus, als müsste ich mein Verständnis von intelligenten Zeigern wirklich klären. Vielen Dank für den Hinweis.
Ikarus