Uma função de destruição pode ser uma função virtual.
3.4. Destrutor virtual (virtual destrutor)
Vamos considerar o seguinte programa:
class B
{ int* a;
public:
B() { a = new int[2];
cout << "constr_B\n"; }
~B(){ delete[] a;
cout << "destr_B\n"; }
};
int main(int argc, char* argv[])
{
B *pb = new D;
delete pb;
return 0;
}
class D : public B
{ int* b;
public:
D() { b = new int[4];
cout << "constr_D\n"; }
~D(){ delete[] b;
cout << "destr_D\n"; }
};
Os resultados:
constr_B
constr_D
destr_B
a = new int[2];
constr_B
constr_D
destr_B
b = new int[4];
delete[] a;
A ordem pela qual os construtores são
chamados é a seguinte:
os primeiros construtores a serem
chamados são os da classe base e só depois
os construtores da classe derivada.
Os destrutores devem ser chamados
exactamente pela ordem inversa.
Neste exemplo temos o problema com
as chamadas dos destrutores.
memória
Este problema é completamente resolvido se o destrutor fosse
virtual.
class B
{ int* a;
public:
B() { a = new int[2];
cout << "constr_B\n"; }
virtual ~B(){ delete[] a;
cout << "destr_B\n"; }
};
class D : public B
{ int* b;
public:
D() { b = new int[4];
cout << "constr_D\n"; }
virtual ~D(){ delete[] b;
cout << "destr_D\n"; }
};
Os resultados:
constr_B
constr_D
destr_D
destr_B
a = new int[2];
constr_B
constr_D
destr_D
destr_B
b = new int[4];
delete[] b;
delete[] a;
Os construtores são chamados pela
seguinte ordem:
os primeiros construtores a serem
chamados são os da classe base e só depois
os construtores da classe derivada.
Os destrutores são chamados
exactamente pela ordem inversa.
memória
Como se pode ver, a destruição foi feita adequadamente. Isto acontece
porque o objecto tem o que se chama tabela de funções virtuais.
Esta tabela é criada para todos os objectos definidos dentro de uma
classe base, e contêm ponteiros para as funções que usam o objecto.
Se o objecto pertencer a uma classe derivada, este contêm uma
tabela adicional para as funções usadas na classe derivada. O que é
preciso reter é que o mecanismo que chama automaticamente o
destrutor baseia-se em endereços fornecidos por aquela tabela e
quando o destrutor é virtual, esse mecanismo automático procura
a tabela de funções virtuais que está no fim (ou seja, a tabela
correspondente à classe derivada), executa esse destrutor passando
de seguida para o destrutor indicado na tabela de funções virtuais
que está directamente acima.
Geralmente, é bom princípio fornecer um destrutor virtual em todas
as classes que funcionam como classes base dado que os objectos
da classe derivada são tipicamente manipulados (e possivelmente
apagados) através de um ponteiro para a base.
Um construtor não pode ser virtual porque necessita informação
acerca do tipo exacto do objecto a ser criado para o poder
construir correctamente. Para além disso, um construtor não é uma
função normal. Esta pode interagir com as rotinas de gestão de
memória de uma forma que funções membro normais não podem, e
é diferente das funções membro normais já que não pode ser
invocada para um objecto já existente. Por consequência não
podemos ter um ponteiro para um construtor.
Podemos definir uma função que chama um construtor e retorna o
objecto construído. Isto é útil porque pode haver necessidade de
criar um objecto sem sabermos o tipo exacto deste.
Vamos considerar um exemplo onde objectos de uma classe podem
fornecer ao utilizador uma cópia de si próprios ou um objecto do
seu tipo.
class expressao
{ public:
virtual expressao* expressao_nova() { return new expressao(); }
expressao(const expressao&);
expressao();
};
class condicao : public expressao
{ public:
expressao* expressao_nova() { return new condicao(); }
condicao(const condicao&);
condicao();
};
void utilizador(expressao* p)
{
expressao* p2 = p->expressao_nova();
}
Isto significa que dado um objecto da classe expressao, um
utilizador pode criar um novo objecto “que é realmente do
mesmo tipo”.
Sumário de construtores
1. Construtores são as rotinas utilizadas em C++ para construir
objectos.
2. Todas as classes têm um construtor.
3. Se numa classe não estiver declarado um construtor o compilador
encarrega-se de implementar um.
4. Os construtores implementados pelo compilador são públicos.
5. O construtor é uma função com o mesmo nome da classe a que
pertence.
6. O construtor pode receber parâmetros.
7. Um construtor da classe X não pode ter parâmetros da classe X,
mas pode ter como parâmetro uma referência à classe X (X&).
8. Um construtor declarado como X::X(const X&) é o construtor de
cópia.
9. O construtor pode ter valores para serem usados por defeito.
10. O construtor não pode devolver qualquer valor, nem mesmo
void.
11.Para que se possa definir um array de objectos de uma classe com
objectos membros declarados constantes, essa classe tem que ter,
no construtor, parâmetros indicados para serem usados por defeito.
12. Não é possível obter um ponteiro para o construtor.
13. Um construtor por defeito é um construtor que pode ser chamado
sem argumentos.
14. O construtor de uma classe é chamado automaticamente quando
um objecto dessa classe é declarado.
15. É possível fazer a sobrecarga (overloading) do nome do
construtor.
16. O construtor não pode ser herdado por classes derivadas; no
entanto as classes derivadas podem chamar o construtor da classe
base.
17. O construtor não pode ser declarado como virtual.
18. Se o construtor, por alguma razão, não termina o seu serviço, o
objecto não é automaticamente destruído.
19. Um objecto com construtor não pode ser membro de uma união.
Sumário de destrutores
1. Destrutores são as rotinas utilizadas em C++ para destruir
objectos.
2. Todas as classes tem um destrutor.
3. Se numa classe não estiver declarado um destrutor o compilador
encarrega-se de implementar um.
4. Os destrutores implementados pelo compilador são públicos.
5. O destrutor é uma função com o mesmo nome da classe a que
pertence, precedido por “~”.
6. O destrutor não pode receber parâmetros (nem mesmo void).
7. O destrutor não pode devolver qualquer valor (nem mesmo void).
8. Não é possível obter um ponteiro para um destrutor.
9. O destrutor de uma classe é chamado automaticamente quando o
programa sai de um bloco (scope) onde um objecto dessa classe foi
declarado.
10. O destrutor pode ser chamado.
11. O destrutor pode ser declarado como virtual.
12. O destrutor não pode ser herdado por classes derivadas, no
entanto as classes derivadas podem chamar os destrutores das
classes base.
13. Um objecto com um destrutor declarado não pode pertencer
a uma união.
14. Para destruir um objecto que esteja na zona de memória
free store deve usar-se o operador delete.
O que é importante:
1. Perceber o que são tipos da memória.
2. Perceber o que são construtores de cópia (copy construtors).
3. Perceber construção e destruição em classes derivadas.
4. Perceber o que é destrutor virtual.
5. Perceber as respectivas regras da linguagem C++.
6. Estar apto a construir programas que utilizem
as respectivas construções de POO.
class my_class
Agora vamos considerar alguns exemplos que
{
char* s;
vão demonstrar algumas construções
int b;
diferentes da linguagem C++
int a;
public:
void display() {cout << a << '\t' << b << '\t' << s << endl;}
my_class(int A, int B, char *S) : a(A), b(B), s(S) {}
virtual ~my_class();
};
void funR(my_class& M)
void fun(my_class M)
{
M.display(); }
{
M.display(); }
int main(int argc, char* argv[])
{
my_class mc=my_class(19,95,"How are you?");
my_class MC(50,2000,"Aveiro");
How are you
mc.display();
MC.display(); 19 95
50 2000 Aveiro
fun( my_class(1,2,"Regards") );
1 2
Regards
funR( my_class(3,4,"Hello") );
3 4
Hello
return 0;
}
class my_class
{
char* s;
int b;
int a;
public:
void display() {cout << a << '\t' << b << '\t' << s << endl;}
void display_hello() {cout << "hello\n"; }
my_class(int A, int B, char *S) : a(A), b(B), s(S) {}
virtual ~my_class();
};
void f(my_class& M, void (my_class::*p)()=my_class::display)
{
M.display();
(M.*p)();
};
int main(int argc, char* argv[])
{
//......................
f(mc);
f(MC,my_class::display_hello);
return 0;
}
19 25
How are you
19 25
How are you
50 2000 Aveiro
hello
class B
{
//................
public:
virtual ~B(){ cout << "destr_B\n";
protected:
B(int);
};
}
int main(int argc, char* argv[])
{
B my_b(3); // cannot access protected member declared in class 'B'
// .................
class D : public B
{
//.....
public:
D(int);
~D(){
cout << "destr_D\n"; }
};
B::B(int for_b)
{
cout << "class B: " << for_b << endl;
}
D::D(int for_d) : B(for_d)
{
cout << "class D: " << for_d << endl;
}
int main(int argc, char* argv[])
{
D my_D(3);
return 0;
}
Os resultados:
class B: 3
class D: 3
Destr_D
Destr_B
DD::DD(int for_dd) : D(for_dd)
{
cout << "class DD: " << for_dd << endl; }
DD::~DD()
{
cout << "destr_DD\n";
int main(int argc, char* argv[])
{
DD my_DD(3);
return 0;
}
}
B
D
DD
Os resultados:
class B: 3
class D: 3
class DD: 3
Destr_DD
Destr_D
Destr_B
Destrutores
Construtores
class DD : public D
{
public:
DD(int);
~DD();
};
Download

ppt1 - Universidade de Aveiro › SWEET