Desafio C++

Evitando ciclo com weak_ptr

Enunciado

Modele uma relação pai-filho onde o pai tem um shared_ptr para o filho e o filho tem um weak_ptr para o pai. Crie as classes Pai e Filho, instancie e verifique que não há vazamento (o destrutor de ambos é chamado).

Requisitos

  • Pai contém std::shared_ptr<Filho>.
  • Filho contém std::weak_ptr<Pai>.
  • Imprimir mensagens nos destrutores para confirmar que ambos são destruídos.
  • Usar lock() para acessar o pai a partir do filho.

Código inicial

#include <iostream>
#include <memory>

// Defina as classes Pai e Filho aqui

int main() {
    // seu código aqui
    return 0;
}

Saída esperada

Pai criado
Filho criado
Acessando pai via weak_ptr: pai
~Pai
~Filho
Ver dica

O weak_ptr não impede a destruição. Use lock() para obter um shared_ptr temporário e acessar o objeto.

Mostrar solução
#include <iostream>
#include <memory>
#include <string>

struct Filho; // forward declaration

struct Pai {
    std::string nome;
    std::shared_ptr<Filho> filho;
    Pai(std::string n) : nome(std::move(n)) {
        std::cout << "Pai criado\n";
    }
    ~Pai() { std::cout << "~Pai\n"; }
};

struct Filho {
    std::string nome;
    std::weak_ptr<Pai> pai; // weak para evitar ciclo
    Filho(std::string n) : nome(std::move(n)) {
        std::cout << "Filho criado\n";
    }
    ~Filho() { std::cout << "~Filho\n"; }
};

int main() {
    auto p = std::make_shared<Pai>("pai");
    auto f = std::make_shared<Filho>("filho");
    p->filho = f;   // pai possui filho
    f->pai = p;     // filho observa pai (weak)

    // Acessa o pai a partir do filho
    if (auto locked = f->pai.lock()) {
        std::cout << "Acessando pai via weak_ptr: " << locked->nome << '\n';
    }
    return 0;
}

Passo a passo

  1. Declaramos struct Filho; antes de Pai porque Pai contém um shared_ptr<Filho>.
  2. Pai tem um shared_ptr<Filho> e um destrutor que imprime ~Pai.
  3. Filho tem um weak_ptr<Pai> e um destrutor que imprime ~Filho.
  4. Em main, criamos p e f com make_shared. O contador de p é 1, o de f é 1.
  5. p->filho = f; incrementa o contador de f para 2. f->pai = p; não altera o contador de p (weak).
  6. f->pai.lock() obtém um shared_ptr temporário para p e acessamos nome.
  7. Ao final de main, f e p são destruídos. O contador de f cai para 0 (destruído), depois p é destruído. Se Filho tivesse shared_ptr<Pai>, haveria ciclo e nenhum seria destruído.

Por que funciona

O weak_ptr não participa da contagem de referências, então não mantém o objeto vivo. Isso quebra o ciclo de posse que ocorreria se ambos usassem shared_ptr. O lock() cria um shared_ptr temporário apenas durante o uso, garantindo que o objeto exista enquanto acessado.

Erros comuns

  • Usar shared_ptr no filho: std::shared_ptr<Pai> pai; cria um ciclo e vaza memória.
  • Esquecer de verificar lock(): se o pai já foi destruído, lock() retorna nullptr; desreferenciar causa erro.
  • Tentar acessar f->pai->nome diretamente: weak_ptr não tem operador ->; é preciso lock().

Outra forma de resolver

Poderia usar std::enable_shared_from_this para obter shared_ptr a partir de this, mas isso não resolve o ciclo; ainda seria necessário weak_ptr em um dos lados.

Saída esperada

Pai criado
Filho criado
Acessando pai via weak_ptr: pai
~Pai
~Filho