Boost Intrusive Pointer (intrusive_ptr)

Agosto 8, 2008

Para ver o artigo anterior da série: Boost Shared Array e Scoped Array

Finalmente chegamos na ultima classe de smart pointer da Boost. O intrusive_ptr é semelhante ao shared_ptr, mas a diferença é que ao invés de alocar um objeto para controlar as referências, ele usa o próprio objeto que ele armazena para esse fim.

Dessa forma, o objeto apontado por ele precisa prover métodos ou funções que cuidem da contagem de referência, por isso o nome de intrusive (ou intruso), porque é necessário modificar o objeto que ele vai armazenar para que se possa usa-lo com o intrusive_ptr, devido a essa forma de implementação, já notamos aqui que não podemos usar o intrusive_ptr com qualquer tipo de objeto como fazíamos com o shared_ptr.

Quando um intrusive_ptr precisa incrementar as referências, ele chama a função intrusive_ptr_add_ref (passando como parâmetro o ponteiro do objeto). No processo inverso, quando é preciso decrementar uma referência, ele invoca a função intrusive_ptr_release, que fica responsável por decrementar as referências e destruir o objeto quando o contador chega a zero.

Vamos então modificar a classe Person para que ela possa ser usada com o intrusive_ptr:


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

class Person
{
    public:
        inline Person():
            m_RefCount(0)
        {
        }

        inline Person(const Person &p):
            m_Name(p.m_Name),
            m_RefCount(0)
        {
        }

        inline const Person &operator=(const Person &p)
        {
            m_Name = p.m_Name;

            return(*this);
        }

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

    private:
        inline void AddRef()
        {
            ++m_RefCount;
        }

        inline void DecRef()
        {
            assert(m_RefCount > 0);

            --m_RefCount;
            if(m_RefCount == 0)
                delete this;
        }

    private:
        std::string m_Name;
        unsigned int m_RefCount;

        friend inline void intrusive_ptr_add_ref(Person *p);
        friend inline void intrusive_ptr_release(Person *p);
};

inline void intrusive_ptr_add_ref(Person *p)
{
    p->AddRef();
}

inline void intrusive_ptr_release(Person *p)
{
    p->DecRef();
}

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

    intrusive_ptr<Person> p(new Person());

    p->SetName("BCS");

    return(0);
}

A classe Person engordou um bocado agora, então vamos por partes:

  1. Foi adicionado o atributo m_RefCount que usamos como contador de referência, usamos um unsigned porque não tem o menor sentido um objeto com contagem negativa.
  2. Tive que adicionar um construtor para inicializar o contador com zero.
  3. Foi preciso também adicionar um construtor de cópia e um operador de atribuição para que o contador seja inicializado e copiado corretamente. Note que no operador de cópia, simplesmente não alteramos o contador.
  4. Criamos os métodos AddRef e DecRef, que incrementam e decrementam o contador, respectivamente. O DecRef possui um assert apenas para tentarmos pegar algum estado inconsistente. Note que o objeto é suicida, se o contador chegou a zero, ele se mata (delete this).
  5. Declaramos as funções intrusive_ptr_add_ref e intrusive_ptr_release para a classe Person, note que elas simplesmente invocam os AddRef e o DecRef. Outro detalhe foi a declaração delas como friend da classe Person, desse modo podemos deixar o acesso ao contador de referências privado e evitar uso equivocado desses métodos.

Vantagens

A primeira vista o intrusive_ptr parece ser uma versão mala do shared_ptr, mas existem muitos casos onde temos um grande numero de objetos e não podemos arcar com o peso de ter uma alocação extra para controle de referencias, nessas horas o intrusive_ptr se torna bem atraente.

Outra vantagem do intrusive_ptr é que podemos usar ele com objetos que já possuem algum controle por contagem de referencia, como por exemplo objetos COM do Windows, podemos simplesmente criar as funções intrusive_ptr_add_ref e intrusive_ptr_release adequadas.

Desvantagens

A maior desvantagem que vejo é que não existe uma implementação do weak_ptr, então é necessário tomar certo cuidado com referências circulares.

Outra desvantagem (que na verdade incomoda apenas quando vamos usa-lo pela primeira vez) é que ele se torna mais trabalhoso que o shared_ptr devido ao código extra para controle de referencias, mas novamente, isso acaba se pagando com as vantagens que temos.

No próximo post vamos (ainda não acabou) ver as funções de casting para smart pointer.