Ponto V!

Home C/C++ Smart Pointers Boost Shared Pointer (shared_ptr)
Bruno Crivelari Sanches
Boost Shared Pointer (shared_ptr)Imprimir
Escrito por Bruno Crivelari Sanches

Para ver o tutorial anterior da série: Auto Pointer (auto_ptr)

Finalmente vamos ver um verdadeiro smart pointer :) . O boost shared pointer é um smart pointer que gerencia a vida dos objetos utilizando contagem de referencias.

A contagem de referencias nada mais é do que um contador associado a cada objeto. Toda vez que um smart pointer passa a apontar para um objeto, ele incrementa esse contador, quando o smart pointer é destruído ou passa a apontar para outro objeto, o contador é decrementado. E por fim, o smart pointer toda vez que decrementar o contador deve verificar o seu valor, se chegou a zero, então ninguém mais referencia aquele objeto, logo ele pode ser destruído.

Vamos ao primeiro exemplo:

#include <boost/shared_ptr.hpp>

int main(int argc, char **argv)
{
    using namespace boost;

    //contador de referencias agora é 1
    shared_ptr<int> p(new int(5));

    //alterando o valor do inteiro
    *p = 3;

    //contador de referencias é 2, p e p2 apontam para o mesmo objeto
    shared_ptr<int> p2 = p;

    return 0;

    //p2 e p sao destruidos, contador de referencias chega a zero e int tambem é destruido.
}

Simples não? Agora vamos criar um pequeno objeto e usa-lo com o shared pointer:

#include <boost/shared_ptr.hpp>
#include <string>

class Person
{
    public:
        inline void SetName(const std::string &name)
        {
            m_Name = name;
        }

    private:
        std::string m_Name;
};

int main(int argc, char **argv)
{
    using namespace boost;

    shared_ptr<person> p(new Person());

    p->SetName("Bruno");

    return 0;
}

Note que não existem diferença nenhuma de uso em relação a um ponteiro normal (assim como os outros smart pointer que vimos anteriormente). Agora vamos fazer uma pequena mudança na classe pessoa:

#include <boost/shared_ptr.hpp>
#include <string> class Person { public: typedef boost::shared_ptr<person> PersonPtr_t; inline void SetName(const std::string &name) { m_Name = name; } inline void SetFilho(PersonPtr_t ptr) { m_Filho = ptr; } inline void SetPai(PersonPtr_t ptr) { m_Pai = ptr; } private: std::string m_Name; PersonPtr_t m_Filho; PersonPtr_t m_Pai; }; int main(int argc, char **argv) { Person::PersonPtr_t pai(new Person()); pai->SetName("Pai"); Person::PersonPtr_t filho(new Person()); filho->SetName("Filho"); pai->SetFilho(filho); return 0; }

Agora criamos dois objetos dinamicamente e ainda associamos um com o outro (pai passou a possuir uma referencia para Filho). No final da execução da função main, o primeiro a ser destruído é o ponteiro filho (lembre-se, em C++ a destruição de variáveis locais é sempre feita na ordem inversa de criação). O ponteiro filho vai decrementar o contador de referencias que vai chegar a um, pois o objeto “Pai” possui também um ponteiro para Filho. Em seguida, o ponteiro pai vai ser destruído, como seu contador de referencias chega a zero, ele destrói o objeto “Pai”, em consequência o destrutor de “Pai” é executado, forçando a destruição das suas variáveis membros, o que significa que o membro “m_Filho” é destruído, nesse momento o contador de referencias do filho finalmente chega a zero e ele é então destruído!.

A sequência de destruição fica parecida com:

filho.~shared_ptr()
DecrementaContador();

pai.~shared_ptr()
DecrementaContador();
Destroy()
Person::~Person()
m_Filho::~shared_ptr()
DecrementaContador()
Destroy()
Person::~Person() //Destrutor do filho é executado

Note que ainda não usei o método SetPai, que vai ser usado no próximo exemplo.

Referências Circulares

No exemplo anterior não utilizei o método SetPai porque ele causa um problema fatal (que vai nos gerar um memory leak):

int main(int argc, char **argv)
{
    Person::PersonPtr_t pai(new Person());
    pai->SetName("Pai");

    Person::PersonPtr_t filho(new Person());
    filho->SetName("Filho");

    pai->SetFilho(filho);    //contador de filho chega a 2
    filho->SetPai(pai);        //contador de pai chega a 2

    return 0;
}

No final do bloco, quando o ponteiro filho é destruído, ele decrementa o contador de referências do filho, que chega a um, nesse caso, o objeto filho não é destruído (lembre-se, o objeto pai também possui uma referência). Em seguida, o ponteiro pai é destruído, ele vai então decrementar o contador de referencia do pai, que também chega a um, logo ele também não é destruído!!!

Resultado: nosso programa não possuí mais nenhuma referência para os objetos, mas como um referência o outro, a contagem de referencia nunca chega a zero, logo eles nunca são destruídos!! É como aquele casal de namorados chatos no telefone:

Ela: Desliga amor
Ele: Não desliga você mor!
Ela: Só desligo depois de você amor!
Ele: Eu também só vou desligar depois de você mor!

Overhead

O shared pointer possui um pequeno overhead toda vez que ele é copiado (pois tem que ficar administrando o contador de referencias). Mas o maior overhead, que pode pesar para algumas aplicações (como jogos em consoles) é a própria criação do contador de referencias.

Toda vez que criamos um novo objeto e associamos ele a um shared_ptr, um objeto para controle de referencias é criado (que é bem pequeno, deve ser do tamanho de um ou dois ponteiros). Mas temos o custo da alocação de memória extra e da possível fragmentação (não sei se a boost internamente usa algum pool para minimizar isso). Mas para a maioria das aplicações, isso é irrelevante.

Na pagina da boost existe uma parte da documentação que mostra o overhead: Smart Pointer Timings.
! Conclusões

Os smart_pointers da boost são excelentes e resolvem muitos dos problemas que enfrentamos no dia dia, eu recomendo muito o seu uso, é muito melhor do que fazer o seu próprio, pois o shared_ptr resolve vários problemas que não tratamos aqui:

  • Exception safe
  • Multithread: é possível que varias threads compartilhem o mesmo objeto, não o mesmo shared_ptr. E isso não torna o seu objeto thread safe, esse problema é do programador!
  • Casts (que vamos ver no futuro)
  • Já foi testado, testado, usado, consertado, …, etc

No próximo tutorial vamos explorar o problema da referencia circular e conhecer mais um smart pointer da boost.

Próximo tutorial da série: Boost Weak Pointer


Comentários (2)
  • Leandro Silva
    avatar

    Cara ótimo post, seu site esta muito bom cheio de informações legais.

    É muito difícil ver esse tipo de conteúdo feito por brasileiros parabéns.

    Já adicionei nos favoritos

  • Bruno Crivelari Sanches
    avatar

    Obrigado Leandro :).

Escrever um comentário
Your Contact Details:
Gravatar enabled
Comentário:
[b] [i] [u] [url] [quote] [code] [img]   
:angry::0:confused::cheer:B):evil::silly::dry::lol::kiss::D:pinch::(:shock:
:X:side::):P:unsure::woohoo::huh::whistle:;):S:!::?::idea::arrow:
Security
Por favor coloque o código anti-spam que você lê na imagem.
LAST_UPDATED2  

Busca

Linguagens

Twitter