quinta-feira, 4 de agosto de 2011

A palavra mágica é: mutable!

mutable é uma keyword do C++ (em C ela não existe) que modifica o comportamento de um atributo de uma classe para que ele possa ser alterado por um método const.

O que é um método const?

Basicamente, declarar uma função como const serve para garantir ao programador que, ao chamá-la, aquele objeto não será modificado.

Vamos usar como exemplo uma classe adorada por professores:


class Pessoa
{
public:
    ...
    // Função declarada corretamente.
    // O objeto "Pessoa" não é modificado.
    int GetIdade() const
    {
        return mIdade;
    }

    // Vai dar erro de compilação aqui.
    // Uma função const tentando modificar
    // um atributo do objeto.
    void SetIdade( int idade ) const
    {
        mIdade = idade;
    }

private:
    int mIdade;
}

Não tem muito segredo. Se uma função for declarada como const (note que a palavra "const" deve vir depois da função, e não antes!), o compilador irá garantir que, ao ser chamada, o objeto não será modificado.
No exemplo acima, a função SetIdade obviamente não pode ser const, pois ela modifica o atributo mIdade.

(Embora este seja o propósito de uma função const, não siga religiosamente a ideia de que elas não modificam o objeto. Existem formas de burlar este comportamento, e eu explicarei em outro post).

Ok, agora vamos ao que interessa: para que serve o mutable?

Basicamente, o mutable permite que uma variável seja modificada por uma função const.
Ou seja, o seguinte exemplo seria perfeitamente válido para o compilador:


class Pessoa
{
public:
    ...
    // Função declarada corretamente.
    // O objeto "Pessoa" não é modificado.
    int GetIdade() const
    {
        return mIdade;
    }

    // Não vai mais dar erro de compilação
    void SetIdade( int idade ) const
    {
        mIdade = idade;
    }

private:
    mutable int mIdade;
}

Argh!! Mas isso não é gambiarra??

Neste exemplo sim, e é totalmente sem sentido. Mas existem situações em que você poderia fazer bom uso disso.

Imagine a seguinte classe:


class String
{
public:
    int GetTamanho() const
    {
        return strlen( mDados );
    }

    void SetDados( char* dados )
    {
        strcpy( mDados, dados );
    }

private:
    char mDados[ 50 ];
}

(a classe está incompleta e não faz os tratamentos de erro necessários, mas vamos deixar isso de lado e nos concentrar no problema).

O usuário, ao pedir o tamanho de uma string, espera que ela não seja modificada. Por isso, ela é const.
Já a SetDados é de se esperar que modifique o conteúdo do objeto, por isso ela não é declarada como const.

A classe está bacana, mas não é muito eficiente. Cada vez que chama a GetTamanho, a função strlen é chamada, e ela tem um custo razoável de processamento.
Não seria melhor se pudéssemos calcular uma vez, aí guardar o último valor calculado e retorná-lo caso a String não fosse modificada?
Algo assim:


class String
{
public:
    ...
    int GetTamanho() const
    {
        if ( mFoiModificado )
        {
            mTamanho = strlen( mDados );
            mFoiModificado = false;
        }
        return mTamanho;
    }

    void SetDados( char* dados )
    {
        mFoiModificado = true;
        strcpy( mDados, dados );
    }

private:
    char mDados[ 50 ];
    int mTamanho;
    bool mFoiModificado;
}

Bem melhor, né?
Mas infelizmente, agora as variáveis mTamanho e mFoiModificado são alteradas pela GetTamanho, por isso ela não poderá mais ser const. Mas, ao mesmo tempo, uma função como "GetTamanho" seria muito interessante que fosse const.

Nesta situação, como somente variáveis auxiliares são modificadas e não irão influenciar no comportamento do objeto em si do ponto de vista do usuário, elas poderiam tranquilamente ser mutables:


class String
{
public:
    ...
    int GetTamanho() const
    {
        if ( mFoiModificado )
        {
            mTamanho = strlen( mDados );
            mFoiModificado = false;
        }
        return mTamanho;
    }

    void SetDados( char* dados )
    {
        mFoiModificado = true;
        strcpy( mDados, dados );
    }

private:
    char mDados[ 50 ];
    mutable int mTamanho;
    mutable bool mFoiModificado;
}

4 comentários:

  1. Obrigado pela explicação da função const, amigo! Simplificou o que os Deitel complicaram demais. Ainda temos mais 3 situações em que se pode usar const. Poderia dar uma explanação? Obrigado.

    ResponderExcluir
  2. Muito obrigado pela explicação, amigo.
    Ela foi bem direta e simples.

    ResponderExcluir
  3. Só uma correção nos dois ultimos exemplos:
    Ao invés de " return mFoiModificado;" na verdade seria " return mTamanho"!

    ResponderExcluir