Smart Pointers para C++ – Shared Pointers

por Fabio A. Mazzarino

Smart Pointers foi um conceito adicionado no C++11 para reduzir os riscos inerentes ao desenvolvimento utilizando ponteiros, como por exemplo liberar um objeto que ainda precisa ser utilizado. No post de hoje vamos falar sobre Shared Pointers.

Shared Pointers são utilizados para gerenciar se um objeto dinamicamente alocado ainda está em uso, liberando a memória quando necessário. Vamos exemplificar para ilustrar melhor:

#include <iostream>
#include <memory>

class AllocTracker {
private:
    std::string name;
public:
    AllocTracker() {
        std::cout << "Anonymous AllocTracker constructor" << std::endl;
        name = "";
    }
    AllocTracker(std::string s) {
        name = s;
        std::cout << name << " AllocTracker constructor" << std::endl;
    }
    ~AllocTracker() {
        if (name == "")
            std::cout << "Anonymous AllocTracker destructor" << std::endl;
        else
            std::cout << name << " AllocTracker destructor" << std::endl;
    }

    void printName() {
        std::cout << "Name: " << name << std::endl;
    }
};

class Dumb {
private:
    AllocTracker *tracker;
public:
    Dumb(AllocTracker *tr) : tracker(tr) { }
    ~Dumb() { if (tracker) delete tracker; } 
    AllocTracker* get() { return tracker; }
};
class Shared {
private:
    std::shared_ptr<AllocTracker> tracker;
public:
    Shared(std::shared_ptr<AllocTracker> tr) : tracker(tr) { }
    std::shared_ptr<AllocTracker> get() { return tracker; }
};

int main() {
    std::cout << "STEP #1" << std::endl;
    AllocTracker *tracker = new AllocTracker("dumb");
    std::cout << "STEP #2" << std::endl;
    Dumb *dumb1 = new Dumb(tracker);
    std::cout << "STEP #3" << std::endl;
    Dumb *dumb2 = new Dumb(dumb1->get());
    std::cout << "STEP #4" << std::endl;
    delete dumb1;
    std::cout << "STEP #5" << std::endl;
    // delete dumb2;

    std::cout << "STEP #6" << std::endl;
    Shared *shared1 = new Shared(std::make_shared<AllocTracker>("shared #1"));
    std::cout << "STEP #7" << std::endl;
    delete shared1;

    std::cout << "STEP #8" << std::endl;
    Shared *shared2 = new Shared(std::make_shared<AllocTracker>("shared #2"));
    std::cout << "STEP #9" << std::endl;
    Shared *shared3 = new Shared(shared2->get());
    std::cout << "STEP #10" << std::endl;
    Shared *shared4 = new Shared(shared2->get());
    std::cout << "STEP #11" << std::endl;
    delete shared2;
    std::cout << "STEP #12" << std::endl;
    delete shared4;
    std::cout << "STEP #13" << std::endl;
    delete shared3;
    std::cout << "STEP #14" << std::endl;
}

Note que vamos utilizar a mesma classe do post sobre Unique Pointers: AllocTracker, que auxilia no rastreamento da alocação e desalocação.

Antes de explicar as classes Dumb e Shared, vamos ver a saída:

STEP #1
dumb AllocTracker constructor
STEP #2
STEP #3
STEP #4
dumb AllocTracker destructor
STEP #5
STEP #6
shared #1 AllocTracker constructor
STEP #7
shared #1 AllocTracker destructor
STEP #8
shared #2 AllocTracker constructor
STEP #9
STEP #10
STEP #11
STEP #12
STEP #13
shared #2 AllocTracker destructor
STEP #14

A classe Dumb utiliza um AllocTracker para demonstrar um problema potencial. No código de exemplo o objeto AllocTracker é compartilhado entre os dois objetos dumb1 e dumb2. Isso geraria uma falha entre STEP #5 e STEP #6, a falha não acontece porque o delete de dumb2 está comentado.

Note que entre STEP #4 e STEP #5 é executado o destrutor de AllocTracker, indicando que o objeto compartilhado foi deletado pelo objeto dumb1. Por conta disso que um delete de dumb2 gera a falha.

A classe Shared também utiliza um AllocTracker, mas para demonstrar como o template shared_ptr pode auxiliar a gerenciar o objeto compartilhado entre as instâncias.

O objeto shared1 não compartilha o objeto de AllocTracker com nenhuma outra instância, portanto entre STEP #8 e STEP #9 podemos conferir a execução de um destrutor.

Em seguida os objetos shared2 e shared3 compartilham uma única instância de AllocTracker, que só é deletada depois de todas as instâncias de Shared que estão usando a mesma instância de AllocTracker são deletadas, que é entre STEP #13 e STEP #14.

Esta é a função do shared_ptr, evitar problemas com o compartilhamento de ponteiros entre diversos objetos.

Neste caso não há muita discussão entre equipes, quando objetos precisam ser compartilhados entre diversos objetos diferentes problemas de gerenciamento envolvendo deleção indevida causam crashes (ou abends, como preferir). Neste caso dificilmente uma equipe irá se opor a sua utilização.