domingo, 6 de novembro de 2011

Ordem de inicialização dos atributos de um objeto

Diz a lenda que o seguinte código dá um erro em tempo de execução:

#include <cstring>

class Foo
{
public:
  Foo ( int size ) :
    mSize(size),
    mValues( new char[mSize] )
    {
      memset( mValues, 0, mSize );
    }
  ~Foo ()
  {
    delete[] mValues;
  }

private:
  char* mValues;
  const int mSize;
};

int main()
{
  Foo deu(10);
  return 0;
}


Você sabe dizer qual é?

O problema aqui está na alocação de memória para mValues. Observe o construtor:

Foo ( int size ) :
  mSize(size),
  mValues( new char[mSize] )
  {
    memset( mValues, 0, mSize );
  }

Aparentemente está ok, não é? No exemplo acima, o objeto é criado passando 10 como parâmetro. Sendo assim, o valor 10 é atribuído para mSize, e depois mValues é alocado com o tamanho de mSize, e então é feito um memset para zerar todos os valores de mValues.
Muito bonito, mas o problema está na ordem de inicialização das variáveis. Quem define esta ordem é a ordem em que as variáveis foram declaradas dentro da classe.

Note:

private:
  char* mValues;
  const int mSize;


Não importa se você vai chamar

Foo ( int size ) :
  mSize(size),
  mValues( new char[mSize] )
{
  ...
}

ou

Foo ( int size ) :
  mValues( new char[mSize] ),
  mSize(size)
{
  ...
}
que mValues sempre vai ser inicializado antes de mSize.

Para resolver o problema?
Inverta a ordem de declaração das variáveis.

O seguinte código:

#include <cstring>

class Foo
{
public:
  Foo ( int size ) :
    mSize(size),
    mValues( new char[mSize] )
  {
    memset( mValues, 0, mSize );
  }
  ~Foo ()
  {
    delete[] mValues;
  }

private:
  const int mSize;
  char* mValues;
};

int main()
{
  Foo deu(10);
  return 0;
}

Não apresenta problema algum.

Dizem que é uma boa prática inicializar as variáveis no construtor na mesma ordem que elas foram declaradas na classe para evitar este tipo de dúvida. Se você tiver paciência para fazê-lo, mande bala! :)