Access Violation

quinta-feira, janeiro 12, 2006

O que esta errado com este código (Parte I) : Resposta
Não fique frustrado se você não conseguiu identificar o problema (muitos desenvolvedores C++ ainda discutem se realmente o que vou expor representa ou não um problema).

A questão aqui é que o objeto X sofre de crise de identidade.

Para entender o problema pense em qual o resultado esperado quando executado o programa. Se você me fizesse esta pergunta a algum tempo atraz eu responderia de pronto:

"Eu espero que seja impresso a string '
[C++] In base constructor I'm derived'
".

Para minha surpresa (e de muitos, pode estar certo), quando executado o programa imprime:

"
'[C++] In base constructor I'm base'.

Entendeu? Não? Eu explico melhor.

Uma vez que o método WhoAmI() foi definido como virtual os desenvolvedores esperam que a chamada a este método no construtor da classe base resolva para o método adequado, ou seja, se estivermos instanciando um objeto da classe base o método WhoAmI() desta classe deveria ser chamado; se estivermos instanciando um objeto da classe derived o método desta classe deveria ser executado. Contudo este não é o comportamento observado ao executar o programa. Na realidade observamos que o método da classe base será executado, não importa se estamos instanciando um objeto desta classe ou de uma subclasse (por exemplo a classe derived). Faça o teste.

Neste momento tenho certeza que já tem alguém dizendo:

"Mas que estúpido! Porque não implementam isto de forma correta?"


Vamos tentar analisar: O padrão C++ dita que um objeto deve ter os construtores de suas classes executados da classe mais base para a classe mais derivada; neste exemplo, quando o objeto x for instanciado os construtores serão executados na ordem:

base --> derived

ou seja, o construtor da classe base será executado primeiro, depois o construtor da classe derived será executado. Neste ponto surge o problema de "crise de identidade" do objeto x. A maioria dos compiladores implementa métodos virtuais através do conceito de vtables (para ser sincero não conheço nenhum que não utilize esta técnica).

[
você não sabe o que é uma vtable? Vou emprestar uma frase feminina milenar:

"se você não sabe o que é uma vtable não sou eu quem vou lhe dizer";

brincadeira a parte, este é um assunto para outro post].

O problema é que neste ponto o (construtor da classe base) o construtor da classe derived ainda não foi executado e, assim sendo, a entrada do método WhoAmI() na vtable do objeto x ainda aponta para o método da classe base.

Somente após a execução do construtor da classe derived é que a vtable será corrida, ou seja, chamadas ao método WhoAmI() serão direcionados ao método desta classe ao invés da classe base. Faça um teste; simplesmente adicione a linha abaixo à função main() do programa de exemplo:



x.WhoAmI();

Observe que será impresso "derived", ou seja, neste ponto a vtable do objeto x já esta correta.

Isto occorre porque objetos nascem como objetos da classe mais base e vão amadurecendo, tomando a identidade de cada uma das classes base deste objeto, a medida que seus respectivos construtores vão sendo executados.

Observe que este comportamento não é decorrente de limitações tecnologicas mas sim de uma descisão consciente para previnir problemas; imagine qual seria o resultado de executar um método virtual de uma classe cujo construtor ainda não tenha sido executado (sim, a ordem de execução dos construtores não pode ser modificada). Neste exemplo não teriamos problema algum, contudo, em qualquer classe útil, possivelmente teríamos variáveis não inicializadas sendo utilizadas (isto realmente não é o comportamento que você deseja pode ter certeza).

Inclusive em algumas linguagens (por exemplo C#) objetos não sofrem desta crise de identidade mas por outro lado podem acessar variáveis não inicializadas se não forem corretamente implementados.

Espero ter sido claro.

[+/-] mostrar/esconder este post