Smart Pointers para C++ – Unique Pointer

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 vazamento de memória. No post de hoje vamos falar sobre Unique Pointers.

Basicamente smart pointers são templates que encapsulam comportamentos específicos para ponteiros, inclusive sobrecarga de operadores como * e ->. Cada tipo de smart pointer tem um comportamento específico.

Unique pointer, ou unique_ptr, garante que no caso de perda da referência para memória alocada não haverá vazamento, mesmo que seja por fim de escopo, ou por reatribuição.

Melhor ilustrar com um exemplo:

#include <iostream>
#include <memory>
#include <string>

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

void leakdumbptr(bool leak) {
    AllocTracker *tracker= new AllocTracker("dumb #1");
    if (!leak)
        delete tracker;
    tracker = new AllocTracker("dumb #2");
    tracker->printName();
    if (!leak)
        delete tracker;
    return;
}

void leakuniqueptr(bool leak) {
    std::unique_ptr<AllocTracker> tracker(new AllocTracker("unique #1"));
    if (!leak)
        delete tracker.release();
    tracker.reset(new AllocTracker("unique #2"));
    tracker->printName();
    if (!leak)
        delete tracker.release();
    return;
}


int main() {
    std::cout << std::endl << "*** CASE #1 - Dumb pointer without leak ***" << std::endl;
    leakdumbptr(false);
    std::cout << std::endl << "*** CASE #2 - Dumb pointer with leak ***" << std::endl;
    leakdumbptr(true);
    std::cout << std::endl << "*** CASE #3 - Unique pointer without leak ***" << std::endl;
    leakuniqueptr(false);
    std::cout << std::endl << "*** CASE #4 - Unique pointer with leak ***" << std::endl;
    leakuniqueptr(true);
}

A classe AllocTracker notifica sempre que o construtor e o destrutor é executado, dessa forma é possível conferir em que sequência são executados. As funções leakdumptr e leakuniqueptr criam um vazamento de memória através da atribuição da alocação de uma nova instância da classe.

O programa acima gera a seguinte saída:

*** CASE #1 - Dumb pointer without leak ***
dumb #1 AllocTracker constructor
dumb #1 AllocTracker destructor
dumb #2 AllocTracker constructor
Name: dumb #2
dumb #2 AllocTracker destructor

*** CASE #2 - Dumb pointer with leak ***
dumb #1 AllocTracker constructor
dumb #2 AllocTracker constructor
Name: dumb #2

*** CASE #3 - Unique pointer without leak ***
unique #1 AllocTracker constructor
unique #1 AllocTracker destructor
unique #2 AllocTracker constructor
Name: unique #2
unique #2 AllocTracker destructor

*** CASE #4 - Unique pointer with leak ***
unique #1 AllocTracker constructor
unique #2 AllocTracker constructor
unique #1 AllocTracker destructor
Name: unique #2
unique #2 AllocTracker destructor

No CASE #1 a execução ocorre sem problemas. No CASE #2 um vazamento de memória é deliberadamente criado ao reatribuir um novo ponteiro sem um delete, e novamente após não desalocar o objeto antes do final do escopo do ponteiro. Este é o caso de referência.

No CASE #3 a execução é equivalente ao CASE #1, novamente ocorre sem problemas. No CASE #4 deliberadamente reatribuimos um novo ponteiro sem um delete, mas note que logo após o construtor do 2o objeto é executado o destrutor do 1o objeto, isso ocorre durante a reatribuição. Em seguida deliberadamente não desalocamos o 2o objeto, novamente note que antes do término da função o destrutor é executado.

Esta é a função do Unique Pointer, evitar problemas de vazamento de memória nesses casos.

Alguns times consideram vazamento de memória um problema secundário, independente do nível de importância que o time dá a vazamentos de memória evitá-los auxilia na estabilidade geral do sistema, poupando custos de manutenção. Utilizar unique pointers da maneira apropriada auxilia na redução das possibilidades de vazamentos de memória. Procure utilizá-los sempre que possível, ou permitido.