std :: shared_ptr de esto

101

Actualmente estoy tratando de aprender a usar punteros inteligentes. Sin embargo, mientras hacía algunos experimentos, descubrí la siguiente situación para la que no pude encontrar una solución satisfactoria:

Imagina que tienes un objeto de clase A que es padre de un objeto de clase B (el hijo), pero ambos deberían conocerse:

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 pregunta es ¿cómo puede un objeto de clase A pasar un std::shared_ptrde sí mismo ( this) a su hijo?

Hay soluciones para los punteros compartidos de Boost ( obteniendo un boost::shared_ptrparathis ), pero ¿cómo manejar esto usando los std::punteros inteligentes?

Ícaro
fuente
2
Como ocurre con cualquier otra herramienta, debe usarla cuando sea apropiado. Usar punteros inteligentes para lo que está haciendo no lo
YePhIcK
De manera similar a impulsar. Vea aquí .
juanchopanza
1
Este es un problema a ese nivel de abstracción. Ni siquiera sabes que "esto" apunta a la memoria en el montón.
Vaughn Cato
Bueno, el idioma no lo hace, pero tú lo haces. Siempre y cuando lleve un registro de dónde está, estará bien.
Alex

Respuestas:

168

Hay std::enable_shared_from_thissolo para este propósito. Usted hereda de él y puede llamar .shared_from_this()desde dentro de la clase. Además, aquí está creando dependencias circulares que pueden provocar pérdidas de recursos. Eso se puede resolver con el uso de std::weak_ptr. Entonces, su código podría verse así (asumiendo que los niños dependen de la existencia del padre y no al revés):

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

Sin embargo, tenga en cuenta que la llamada .shared_from_this()requiere que thissea ​​propiedad de std::shared_ptren el punto de llamada. Esto significa que ya no puede crear dicho objeto en la pila y, por lo general , no puede llamar .shared_from_this()desde un constructor o destructor.

yuri kilochek
fuente
1
Gracias por su explicación y por señalar mi problema de dependencia circular.
Ícaro
@Deduplicator ¿a qué te refieres?
yuri kilochek
Intente construir un shared_ptrbasado en un construido por defecto shared_ptry lo que sea que desee señalar ...
Desduplicador
1
@Deduplicator eso es, perdón por mi juego de palabras, un puntero compartido bastante inútil. Ese constructor está destinado a ser utilizado con punteros a miembros del objeto gestionado o sus bases. En cualquier caso, ¿cuál es tu punto (lo siento)? Estos no propietarios shared_ptrson irrelevantes para esta pregunta. shared_from_thisLas condiciones previas establecen claramente que el objeto debe ser propiedad (no solo señalado) por algunos shared_ptren el punto de llamada.
yuri kilochek
1
@kazarey shared_ptrSe requiere la propiedad de a en el punto de llamada, pero en un patrón de uso típico, es decir, algo como shared_ptr<Foo> p(new Foo());, shared_ptrasume la propiedad del objeto solo después de que esté completamente construido. Es posible evitar esto creando un shared_ptrconstructor inicializado con thisy almacenándolo en algún lugar no local (por ejemplo, en un argumento de referencia) para que no muera cuando el constructor se complete. Pero es poco probable que este enrevesado escenario sea necesario.
yuri kilochek
9

Tiene varios problemas en su diseño, que parecen provenir de una mala comprensión de los punteros inteligentes.

Los punteros inteligentes se utilizan para declarar la propiedad. Está rompiendo esto al declarar que ambos padres son dueños de todos los niños, pero también que cada niño es dueño de su padre. Ambos no pueden ser verdad.

Además, está devolviendo un puntero débil en getChild() . Al hacerlo, declaras que la persona que llama no debería preocuparse por la propiedad. Ahora bien, esto puede ser muy limitante, pero también al hacerlo, debe asegurarse de que el niño en cuestión no sea destruido mientras aún se mantienen los punteros débiles, si usara un puntero inteligente, se resolvería por sí solo .

Y lo último. Por lo general, cuando acepta nuevas entidades, generalmente debe aceptar punteros sin formato. El puntero inteligente puede tener su propio significado para intercambiar hijos entre padres, pero para uso general, debe aceptar punteros sin formato.

Šimon Tóth
fuente
Parece que realmente necesito aclarar mi comprensión de los punteros inteligentes. Gracias por señalar eso.
Ícaro