Rodrigo Strauss :: Blog
Por que o bug?
Nós tínhamos um bug, que acontecia quando inseríamos (push_back) um objeto de uma determinada classe dentro de um container STL. Quando líamos o valor da variável, ela não correspondia ao valor do objeto inserido no container, e a runtime do C++ gerava um assert dizendo que estávamos chamando delete para um objeto mais de uma vez.
O problema nesse caso, foi causado por algo que o C++ não costuma fazer: um código gerado pelo compilador, algo que não foi você que fez. Nesse caso, o copy constructor. O copy constructor é um construtor especial, que é chamado quando um objeto é copiado. Isso acontece quando você atribui um objeto a outro, retorna um objeto de uma função, ou passa um objeto para uma função como valor. Um trecho de código vale mais que (pi^10) palavras:
class X
{
public:
int i;
};
X func(X obj)
{
X localx;
// mais uma cópia
localx = obj;
localx.i = obj.i;
// oh, estamos copiando novamente!
return localx;
}
int main()
{
X x1, x2;
x2.i = 10;
// copiando...
x1 = func(x2);
return 0;
}
Na função "func" do exemplo acima, existem 3 operações de cópia: uma quando passamos x2 como parâmetro, outra quando atribuimos o parâmetro obj a localx, e outra na hora de retornar localx. Nessas situações, o copy constructor é chamado para copiar o objeto em questão. Como no nosso exemplo não temos um copy constructor definido, o compilador gera um automaticamente. Olhe como fica a nossa classe X com um copy constructor, equivalente ao que é gerado pelo compilador:
class X
{
public:
//
// se definirmos um copy constructor, o compilador não gerará mais
// o construtor default. Então vamos fazê-lo
//
X()
{}
//
// copy constructor, que tem a sintaxe [tipo(const tipo& param)]
// esse copy constructor é equivalente ao gerado pelo compilador
//
X(const x& v)
{
i = v.i;
}
int i;
};
Para nossa classe X, o copy constructor não gera problemas. Agora, vamos ver o copy constructor equivalente ao gerado pelo compilador para nossa classe com bug:
class CTest2
{
private:
CTest1* m_pTest1;
public:
...
//
// copy constructor equivalente ao gerado pelo compilador
//
CTest2(const CTest2& v)
{
m_pTest1 = v.m_pTest1;
}
...
~CTest2()
{
delete m_pTest1;
}
};
Note que m_pTest1 é a única variável membro de CTest2. Então a única coisa que é feita é copiar o valor dessa variável (que é um ponteiro). Note que - isso é importante - o construtor não é rodado no caso de cópia de objeto. Sendo assim, o objeto cópia não terá um ponteiro alocado com new, mas sim, a cópia do ponteiro do objeto do qual ele foi copiado. Assim, tentaremos chamar delete para o mesmo ponteiro, mas nas duas instâncias de CTest2 - o que gera o assert que falei.
Se você está se perguntando onde é feita a cópia no código do exemplo do bug, repare que eu criei um objeto temporário diretamente ao invés de criar um objeto:// // criamos um objeto temporário do tipo CTest2, chamando // o construtor para inicializá-lo // vecTest2.push_back(CTest2(100, "1bit"));
O construtor do nosso objeto temporário é executado logo antes da chamada da função (push_back), e o destrutor é chamado logo após o retorno da função. A função push_back espera uma referência para o objeto (const CTest2&), então não é feita a cópia durante a passagem de parâmetros. Mas o objeto é copiado ao ser inserido no vector<>, o que faz com que o copy constructor gerado seja chamado, e copie o valor do ponteiro.
Uma das possíveis soluções é criarmos um copy constructor para nossa classe com bug, fazendo com que um novo objeto CTest1 seja criado durante a cópia. Assim, cada classe pode chamar delete para o seu ponteiro. Nossa classe ficaria assim:
class CTest2
{
private:
CTest1* m_pTest1;
public:
...
CTest2(const CTest2& v)
{
m_pTest1 = new CTest1();
//
// por falar em copy constructor, essa instrução chamará o copy constructor
// da classe CTest1, copiando todos os membros
//
*m_pTest1 = v.m_pTest1;
}
...
~CTest2()
{
delete m_pTest1;
}
};
Nossa solução é eficaz nesse caso. Mas e se precisássemos que as duas cópias usassem o mesmo ponteiro? Aguarde os próximos posts.
Em 09/04/2005 17:43, por Rodrigo Strauss





Cara, que coincidência, eu tava tava dando uma olhada em um problema no copy construtor agora pouco e postei no MSDN forum.
Fora o copy construtor tem também o conversion construtor que também faz coisas implícitas.