std :: shared_ptr deste

101

Atualmente, estou tentando aprender como usar ponteiros inteligentes. No entanto, ao fazer alguns experimentos, descobri a seguinte situação para a qual não consegui encontrar uma solução satisfatória:

Imagine que você tenha um objeto da classe A sendo pai de um objeto da classe B (o filho), mas ambos deveriam se conhecer:

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

A questão é como um objeto da classe A pode passar um std::shared_ptrde si mesmo ( this) para seu filho?

Existem soluções para ponteiros compartilhados do Boost ( Obtendo um boost::shared_ptrparathis ), mas como lidar com isso usando std::ponteiros inteligentes?

Icaro
fonte
2
Como acontece com qualquer outra ferramenta, você deve usá-la quando for apropriado. Usar ponteiros inteligentes para o que você está fazendo não
YePhIcK
Da mesma forma para impulsionar. Veja aqui .
juanchopanza
1
Este é um problema nesse nível de abstração. Você nem sabe que "isso" aponta para a memória no heap.
Vaughn Cato
Bem, a linguagem não, mas você sim. Contanto que você acompanhe o que está onde está, você ficará bem.
Alex

Respostas:

168

Existe std::enable_shared_from_thisapenas para esse efeito. Você herda dele e pode chamar .shared_from_this()de dentro da classe. Além disso, você está criando dependências circulares que podem levar a vazamentos de recursos. Isso pode ser resolvido com o uso de std::weak_ptr. Portanto, seu código pode ser assim (assumindo que os filhos dependem da existência do pai e não o contrário):

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

Observe, entretanto, que a chamada .shared_from_this()requer que thisseja propriedade de std::shared_ptrno ponto de chamada. Isso significa que você não pode mais criar tal objeto na pilha e geralmente não pode chamar .shared_from_this()de dentro de um construtor ou destruidor.

yuri kilochek
fonte
1
Obrigado por sua explicação e por apontar meu problema de dependência circular.
Ícaro
@Deduplicator o que você quer dizer?
yuri kilochek
Tente construir um shared_ptrbaseado em um padrão construído shared_ptre o que você quiser apontar para ...
Deduplicator
1
@Deduplicator que é um, com perdão meu trocadilho, ponteiro compartilhado bastante inútil. Esse construtor deve ser usado com ponteiros para membros do objeto gerenciado ou suas bases. Em qualquer caso, qual é o seu ponto (sinto muito)? Esses não-proprietários shared_ptrsão irrelevantes para esta questão. shared_from_thisAs pré-condições de afirmam claramente que o objeto deve pertencer (não apenas ser apontado) por alguns shared_ptrno momento da chamada.
yuri kilochek
1
@kazarey A propriedade por a shared_ptré exigida no momento da chamada, mas em um padrão de uso típico, ou seja, algo como shared_ptr<Foo> p(new Foo());, shared_ptrassume a propriedade do objeto somente após ele ser totalmente construído. É possível contornar isso criando shared_ptrno construtor inicializado com thise armazenando-o em algum lugar não local (por exemplo, em um argumento de referência) para que não morra quando o construtor for concluído. Mas é improvável que esse cenário complicado seja necessário.
yuri kilochek
9

Você tem vários problemas em seu design, que parecem resultar de sua compreensão equivocada de ponteiros inteligentes.

Ponteiros inteligentes são usados ​​para declarar propriedade. Você está quebrando isso ao declarar que ambos os pais são donos de todos os filhos, mas também que cada filho possui seus pais. Ambos não podem ser verdade.

Além disso, você está retornando um indicador fraco em getChild(). Ao fazer isso, você está declarando que o chamador não deve se preocupar com a propriedade. Agora, isso pode ser muito limitante, mas também ao fazer isso, você deve se certificar de que a criança em questão não será destruída enquanto quaisquer ponteiros fracos ainda estiverem sendo pressionados, se você usasse um ponteiro inteligente, ele seria resolvido por si mesmo .

E a coisa final. Normalmente, quando você está aceitando novas entidades, normalmente deve aceitar ponteiros brutos. O ponteiro inteligente pode ter seu próprio significado para trocar filhos entre pais, mas para uso geral, você deve aceitar ponteiros brutos.

Šimon Tóth
fonte
Parece que realmente preciso esclarecer meu entendimento sobre as dicas inteligentes. Obrigado por apontar isso.
Ícaro