Upcasting Upcasting
Quando o endereço de um objecto derivido (i.e. um ponteiro ou uma referência) é tratado como se fosse o endereço do tipo base, este processo chama-se upcasting.
Exemplo:
aluno a;
empregado e;
pessoa* pa = &a;
pessoa& re = e;
class pessoa { public:
void anunciar ()
{ cout << "Sou pessoa!" << endl;
}; }
class aluno : public pessoa { public:
void anunciar ()
{ cout << "Sou aluno!" << endl;
}; }
void anuncio (pessoa& p) { p.anunciar();
}
int main (int argc, char* argv[])
{ aluno a1;
anuncio (a1);
return 0;
}
Sou pessoa!
? binding
early late
Para causar a ligação dinâmica duma função particular usa-se a palavra-chave virtual .
A ligação dinâmica só ocorre para as funções virtuais e desde que seja utilizado o endereço daquela classe base na qual existem estas funções virtuais.
Para criar uma função-membro como virtual, a declaração desta função deve ser precedida pela palavra virtual .
class pessoa { public:
virtual void anunciar ();
};
pessoa.h
void pessoa::anunciar()
{ cout << "Sou pessoa!" << endl;
};
pessoa.cpp
class pessoa { public:
virtual void anunciar ();
};
pessoa.h
Se uma função é definida como virtual numa classe base, esta função é também virtual em todas as classes derivadas.
class aluno : public pessoa { public:
void anunciar ();
};
aluno.h
void aluno::anunciar()
{ cout << "Sou aluno!" << endl;
};
aluno.cpp void pessoa::anunciar()
{ cout << "Sou pessoa!" << endl;
};
pessoa.cpp
int main (int argc, char* argv[])
{ aluno a1;
anuncio (a1); Sou aluno!
Extensibilidade
Extensibilidade
void anuncio (pessoa& p) { p.anunciar();}
Quando anunciar() é definida como virtual na classe base ( pessoa ) torna-se possível adicionar tipos novos (derivados da pessoa ) sem necessidade de modificar a função anuncio() .
class aluno_pos_gr : public aluno { public:
void anunciar ();
};
aluno_pos_gr.h
void aluno_pos_gr::anunciar() {
cout << "Sou aluno de "
"pós-graduação!" << endl;
};
aluno_pos_gr.cpp
int main (int argc, char* argv[])
{ aluno_pos_gr a2;
anuncio (a2);
return 0;
}
Sou aluno de pós-graduação!
Os programas tornam-se extensíveis porque podemos adicionar funcionalidades novas herdando os tipos de dados novos de uma classe base comum.
As funções que manipulam a interface da classe base não precisam
de ser modificadas para poderem suportar as classes novas!
class aluno_pos_gr : public aluno { public:
void anunciar ();
};
aluno_pos_gr.h
void aluno_pos_gr::anunciar() {
cout << "Sou aluno de "
"pós-graduação!" << endl;
};
aluno_pos_gr.cpp
int main (int argc, char* argv[]) { aluno_doutoramento a3;
anuncio (a2); Sou aluno de pós-graduação!
class doutoramento : public aluno_pos_gr {
};
aluno_doutoramento.h
pessoa aluno
aluno_pos_gr
aluno_doutoramento
Implementação da ligação dinâmica Implementação da ligação dinâmica
Um compilador típico cria uma tabela (VTABLE) para cada classe que contém funções virtuais.
Nesta tabela põem-se os endereços de todas as funções virtuais desta classe particular.
Em cada objecto da classe com funções virtuais o compilador cria um ponteiro (vpointer) que aponta para a VTABLE deste objecto.
Quando se efectua a chamada de uma função virtual através do ponteiro para a classe base, o compilador gera código que permita aceder ao vpointer e determinar o endereço da função em
VTABLE invocando deste modo a função correcta e causando a
ligação dinâmica.
class NoVirtual
{ int a;
public:
void x() const {}
int i() const { return 1; } };
class OneVirtual
{ int a;
public:
virtual void x() const {}
int i() const { return 1; } };
class TwoVirtuals
{ int a;
public:
virtual void x() const {}
virtual int i() const { return 1; } };
int main()
{ cout << "int: " << sizeof(int) << endl;
cout << "NoVirtual: " << sizeof(NoVirtual) << endl;
cout << "void* : " << sizeof(void*) << endl;
cout << "OneVirtual: " << sizeof(OneVirtual) << endl;
cout << "TwoVirtuals: " << sizeof(TwoVirtuals) << endl;
int: 4
NoVirtual: 4 void*: 4
OneVirtual: 8
TwoVirtual: 8
pessoa* A [] = { new pessoa, new aluno, new aluno_pos_gr, new aluno_doutoramento, };
pessoa
vptr
VTABLES:
&pessoa::anunciar Objectos:
aluno
vptr
&aluno::anunciar
aluno_pos_gr
vptr
&aluno_pos_gr ::anunciar
aluno_doutoramento
vptr
&aluno_pos_gr ::anunciar Array de
ponteiros
para pessoa:
class pessoa
{ public:
virtual void anunciar ()
{ cout << "Sou pessoa!" << endl;
} virtual void print_me();
virtual ~pessoa();
};
class aluno : public pessoa
{ public:
void anunciar ()
{ cout << "Sou aluno!" << endl;
} void print_me();
~aluno();
};
VTABLE:
Objecto:
aluno
vptr
&aluno :: anunciar ponteiro
para pessoa:
[0]
&aluno :: print_me
[1]&
aluno:: ~aluno
[2]mov ecx,dword ptr [ebp-14h]
call @ILT+160(pessoa::print_me) (004010a5) não virtual:
chamar função pessoa::print_me
aluno a; pessoa& pr = a; pr.print_me();
virtual:
chamar função no endereço VPTR[1]
mov ecx,dword ptr [ebp-14h]
mov edx,dword ptr [ecx]
mov esi,esp
mov ecx,dword ptr [ebp-14h]
call dword ptr [edx+4]
cmp esi,esp
call __chkesp (004038d0)
O vpointer é inicializado no construtor da classe automaticamente.
O upcasting só funciona com os endereços. Quando o compilador tem o objectos, ele conhece o tipo exacto e portanto não precisa da ligação dinâmica.
int main() {
aluno a;
a.anunciar();
pessoa& pr = a;
pr.anunciar();
pessoa* pp = &a;
pp.anunciar();
return 0;
ligação estática
ligação dinâmica
ligação dinâmica