std :: shared_ptr di questo


101

Attualmente sto cercando di imparare a utilizzare i puntatori intelligenti. Tuttavia durante alcuni esperimenti ho scoperto la seguente situazione per la quale non sono riuscito a trovare una soluzione soddisfacente:

Immagina di avere un oggetto di classe A che è genitore di un oggetto di classe B (il bambino), ma entrambi dovrebbero conoscersi:

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;
};

La domanda è: come può un oggetto di classe A passare un std::shared_ptrdi se stesso ( this) a suo figlio?

Esistono soluzioni per Boost shared pointers ( Getting a boost::shared_ptrforthis ), ma come gestirlo utilizzando i std::puntatori intelligenti?


2
Come con qualsiasi altro strumento, devi usarlo quando è appropriato. Usare puntatori intelligenti per quello che stai facendo non lo
YePhIcK

Allo stesso modo per aumentare. Vedi qui .
juanchopanza

1
Questo è un problema a quel livello di astrazione. Non sai nemmeno che "questo" punta alla memoria nel mucchio.
Vaughn Cato

Ebbene, la lingua no, ma tu sì. Finché tieni traccia di cosa è dove, starai bene.
Alex

Risposte:


168

C'è std::enable_shared_from_thissolo per questo scopo. Tu erediti da esso e puoi chiamare .shared_from_this()dall'interno della classe. Inoltre, qui stai creando dipendenze circolari che possono portare a perdite di risorse. Questo può essere risolto con l'uso di std::weak_ptr. Quindi il tuo codice potrebbe assomigliare a questo (supponendo che i bambini si basino sull'esistenza del genitore e non il contrario):

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;
};

Si noti tuttavia che la chiamata .shared_from_this()richiede che thissia di proprietà di std::shared_ptral punto di chiamata. Ciò significa che non è più possibile creare tale oggetto sullo stack e generalmente non è possibile chiamare .shared_from_this()dall'interno di un costruttore o distruttore.


1
Grazie per la tua spiegazione e per aver sottolineato il mio problema di dipendenza circolare.
Icaro

@Deduplicator cosa intendi?
yuri kilochek

Prova a costruire un shared_ptrbasato su un valore predefinito shared_ptre qualunque cosa tu voglia indicarlo ...
Deduplicator

1
@Deduplicator è un, scusate il mio gioco di parole, un puntatore condiviso piuttosto inutile. Quel costruttore è destinato a essere utilizzato con puntatori ai membri dell'oggetto gestito o alle sue basi. In ogni caso qual è il tuo punto (mi dispiace)? Questi messaggi non di proprietà shared_ptrsono irrilevanti per questa domanda. shared_from_thisLe condizioni preliminari stabiliscono chiaramente che l'oggetto deve essere di proprietà (non solo indicato) da alcuni shared_ptral momento della chiamata.
yuri kilochek

1
@kazarey La proprietà di a shared_ptrè richiesta al momento della chiamata, ma in un modello di utilizzo tipico, cioè qualcosa di simile shared_ptr<Foo> p(new Foo());, shared_ptrassume la proprietà dell'oggetto solo dopo che è stato completamente costruito. È possibile aggirare questo problema creando shared_ptrnel costruttore inizializzato con thise memorizzandolo da qualche parte non locale (ad esempio in un argomento di riferimento) in modo che non muoia quando il costruttore completa. Ma è improbabile che questo scenario contorto sia necessario.
yuri kilochek

9

Hai diversi problemi nella progettazione, che sembrano derivare da una tua incomprensione dei puntatori intelligenti.

I puntatori intelligenti vengono utilizzati per dichiarare la proprietà. Stai rompendo questo dichiarando che entrambi i genitori possiedono tutti i figli, ma anche che ogni bambino possiede il proprio genitore. Entrambi non possono essere veri.

Inoltre, stai restituendo un puntatore debole in getChild(). In questo modo, dichiari che il chiamante non dovrebbe preoccuparsi della proprietà. Ora questo può essere molto limitante, ma anche così facendo, devi assicurarti che il bambino in questione non venga distrutto mentre sono ancora presenti puntatori deboli, se usassi un puntatore intelligente, verrebbe risolto da solo .

E l'ultima cosa. Di solito, quando accetti nuove entità, di solito dovresti accettare puntatori non elaborati. Il puntatore intelligente può avere il proprio significato per lo scambio di figli tra genitori, ma per un utilizzo generale, dovresti accettare i puntatori non elaborati.


Sembra che abbia davvero bisogno di chiarire la mia comprensione dei puntatori intelligenti. Grazie per averlo fatto notare.
Icaro
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.