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.
3. Construtores e destrutores
3.1. Tipos da memória
3.2. Construtores de cópia (copy construtor)
3.3. Chamada de construtores para classes bases e derivadas
(construção e destruição em classes derivadas)
3.4. Destrutor virtual
3.1. Tipos da memória.
Em C++ podem ser construídos quatro tipos de objectos:
1. Objectos automáticos são objectos que são construídos quando
são declarados e destruídos quando se sai do bloco (scope) dentro
de qual foram declarados. Os objectos automáticos são colocados
no stack.
2. Objectos estáticos são objectos que são criados quando o
programa começa e são destruídos quando o programa termina.
3. Objectos criados na zona “free store” são objectos que são criados
através de comandos de reserva dinâmica de memória (o operador
new). O espaço de memória é reservado na zona denominada “free
store” (ou “heap”) e só pode ser libertado com o operador delete.
4. Objectos membros são objectos que são criados como membros
de uma classe ou como elementos de um array.
De seguida vou mostrar quando são chamados os construtores e
destrutores para cada um destes tipos de objectos que surgem
ao longo de um programa.
O construtor para uma variável local é executado cada vez que a
linha de controlo passa pela declaração da variável local. O destrutor
para uma variável local é executado cada vez o programa sair do
bloco onde foi declarada essa variável local.
cout << "--------------\n";
{
expressao block1;
cout << "--------------\n";
{
expressao block2;
cout << "--------------\n";
Os resultados:
}
}
-------------construtor
destrutor
-------------construtor
destrutor
--------------
Os construtores para “global static objects” num ficheiro são
executados pela ordem em que cada uma das declarações ocorre;
os destrutores são chamados pela ordem inversa.
Os construtores para objectos locais estáticos são chamados na
primeira vez que a linha de controlo passa pela definição do objecto.
O operador new para o objecto invoca o respectivo construtor.
O operador delete para o objecto chama o respectivo destrutor.
Para declarar um array de objectos de uma classe com um
construtor, essa classe tem de ter um construtor por defeito. Não
há maneira de especificar argumentos para um construtor numa
declaração de um array. O destrutor tem de ser chamado para
cada elemento de um array quando esse array é destruído. Isto é
feito implicitamente para os arrays que não foram reservados em
memória através da utilização do operador new.
Construtores
A função de construção tem de ter o mesmo nome que a classe para
a qual a função é declarada. Isto é, class A {...} tem de ter uma
função de construção A().
A função de construção não pode ter um tipo de retorno.
Uma função de construção pode ter argumentos por defeito. Os
valores dos argumentos por defeito aparecem na declaração da
função de construção, não na definição. A sintaxe tem o aspecto
do seguinte exemplo:
Vamos declarar um array de objectos.
class A
{
Neste caso temos de usar um
//.................
construtor por defeito (porque não
A(int, int=2,char=‘q’);
podemos especificar uma lista de
//................
argumentos para cada objecto).
Por defeito pode significar tanto um
}
construtor sem argumentos como
A::A(int i1, int i2,char c1)
um construtor com todos os
{................................}
argumentos por defeito.
Não é possível obter o endereço de uma função de construção.
Uma função de construção não pode ser declarada como static,
const, ou volatile. Uma função de construção tem de ser uma função
membro não estática porque as funções de construção não podem
existir separadamente dos objectos particulares.
Uma função de construção não pode ser herdada. É única para a
sua classe. Quando um objecto de uma classe derivada é inicializado,
o construtor da classe base é invocado primeiro, e só depois o
construtor da classe derivada é invocado também.
Um “copy construtor”, tal como um construtor por defeito, pode ser
gerado pelo compilador.
Funções membro da classe podem ser chamadas de dentro de uma
função de construção, e objectos membros da classe podem ser
referenciados dentro de uma função de construção.
3.2. Construtores de cópia (copy construtor)
O “copy construtor” para uma classe é um caso especial em que o
argumento é uma referência para um objecto da mesma classe. Uma
maneira diferente de construir um objecto pode ser a seguinte:
pessoa p1(21,”Paulo”);
pessoa p2 = p1;
O primeiro objecto (p1) é construído através do construtor
implementado para a classe pessoa. O segundo objecto (p2) é
construído de outra forma. O que se pretende ao declarar p2 desta
forma é criar uma réplica de p1. Para isso existe outro tipo de
construtor, o construtor de cópia (copy construtor). O que o
construtor de cópia por defeito (default copy construtor) do
compilador faz é reservar espaço no stack para p2 (tal como faria o
construtor por defeito do compilador) e copiar para esse espaço p1.
O objecto a copiar pertence à classe pessoa. O objecto possui
portanto um ponteiro com o mesmo valor.
memória
memória
char* Nome
Objecto p1
char* Nome
Objecto p2
P
a
u
l
o
NULL
Quando a função onde os objectos foram definidos acabar a sua
execução os objectos vão ser destruídos. O primeiro a ser destruído
é o objecto p2. A ordem de destruição dos objectos é a inversa da
ordem de construção. Quando o objecto p2 for destruído é chamado
o destrutor do objecto. O destrutor liberta a zona de memória onde
está o Nome. Em seguida vai ser destruído o objecto p1. Quando o
destrutor de p1 tenta libertar a zona da memória ocupada pelo
Nome esta já não lhe pertence, porque foi libertada por p2.
Mais uma vez, a melhor maneira de resolver este problema passa
pela implementação de um construtor próprio para o objecto. Assim,
o construtor de cópia para a classe pessoa será:
class pessoa
{
unsigned short Idade; char* Nome;
public:
pessoa(unsigned short, char*);
pessoa(pessoa&);
pessoa(const
pessoa&);
//.........................
};
pessoa::pessoa(const pessoa &p)
{
Nome = new char[strlen(p.Nome)+1];
strcpy(Nome,p.Nome);
Idade = p.Idade;
}
O construtor de cópia é declarado como um construtor normal, só
que como parâmetro de entrada recebe a referência de um objecto
da mesma classe (o objecto a copiar). Na implementação segue os
mesmos passos do construtor normal, só que inicializa os objectos
membros com os valores do objecto a copiar. Aos objectos que são
ponteiros para zonas de memória conseguidas com o operador new,
é dado o valor de novas zonas de memória reservadas para o novo
objecto, de forma a evitar o problema referido.
A outra situação onde se usa o construtor de cópia é na chamada de
funções que recebem objectos da classe como parâmetros.
void enviar_dados(pessoa);
void main(void)
{
pessoa p(25,”Paulo”);
enviar_dados(p);
// .............................
}
A função enviar_dados recebe como parâmetro o objecto p. Para o
usar vai ter que criar na sua região do stack uma cópia do objecto
original. Como a cópia do objecto é destruída quando a função
terminar, se o construtor de cópia não for o adequado pode surgir
o mesmo problema do caso anterior.
3.3. Chamada de construtores para classes bases e
derivadas (construção e destruição em classes derivadas)
Podemos construir uma classe aluno derivada da classe pessoa.
class aluno : public pessoa
{
int grupo;
public:
//...............
};
Tem interesse definir também para esta classe um construtor, um
destrutor e um construtor de cópia.
Como a classe aluno é uma classe derivada, contém todos os
elementos da classe base. Por isso, dentro dos construtores e do
destrutor da classe aluno é feita a chamada aos construtores e ao
destrutor (respectivamente) da classe base (pessoa).
class aluno : public pessoa
{
int grupo;
public:
aluno(unsigned short Id, char* No, int gr);
aluno(const aluno&); // o construtor de cópia
~aluno();
//...............
};
aluno::aluno(unsigned short Id, char* No, int gr) :
pessoa(Id,No)
{
grupo = gr;
}
aluno::aluno(const aluno& p) : pessoa(p)
{
grupo = p.grupo;
}
pessoa::pessoa(const pessoa &p)
copy constructor for pessoa
//...................
{ Nome = new char[strlen(p.Nome)+1];
strcpy(Nome,p.Nome);
Idade = p.Idade;
cout << "copy constructor for pessoa\n";
}
class pessoa
int main(int argc, char* argv[])
{
{
protected:
aluno a(21, "Ricardo", 6251);
unsigned short Idade;
copy_demo(a);
char* Nome;
a.print_me();
public:
copy_demo_ref(a);
pessoa(const pessoa&); //.........................................
//......................................
void copy_demo(aluno ALUNO)
class aluno : public pessoa
{}
{
void copy_demo_ref(aluno& ALUNO)
public:
//
aluno(const aluno&); {}
Erro: No copy constructor available for class 'aluno'
class pessoa
int main(int argc, char* argv[])
{
{
protected:
aluno a(21, "Ricardo", 6251);
unsigned short Idade;
copy_demo(a);
char* Nome;
a.print_me();
private:
copy_demo_ref(a);
pessoa(const pessoa&); //.........................................
//......................................
void copy_demo(aluno ALUNO)
class aluno : public pessoa
{}
{
void copy_demo_ref(aluno& ALUNO)
public:
//
aluno(const aluno&); {}
aluno::aluno(const aluno &p) : pessoa(p)
{
grupo = p.grupo;
cout << "copy constructor for aluno\n";
pessoa::~pessoa()
{
delete[] Nome;
cout << "destructor for pessoa\n";
}
class aluno : public pessoa
{
public:
aluno(const aluno&);
copy constructor for pessoa
copy constructor for aluno
//...................
destructor for aluno
destructor for pessoa
}
aluno::~aluno()
{
cout << "destructor for aluno\n";
}
int main(int argc, char* argv[])
{
aluno a(21, "Ricardo", 6251);
copy_demo(a);
a.print_me();
copy_demo_ref(a);
//.........................................
Destrutores
A função de destruição tem o mesmo nome que a sua classe,
excepto que o seu nome é precedido por um til (~). Podemos então
declarar um destrutor da seguinte maneira:
class A
{
//......................
~A(); // destrutor para A
};
A função de destruição pode estar “inline”. Se estiver “out-of-line”,
a sua definição será escrita do seguinte modo:
A::~A()
{
//..............
}
A finalidade da função de destruição é oposta à de construção.
O destrutor “des-inicializa” um objecto.
Uma função de destruição não pode ter um tipo de retorno. Tal
como para um construtor, o compilador gera o seu próprio e
interno tipo para o destrutor. Não nos é permitido escrever um
código que tenha como retorno um tipo.
Ao contrário da função de construção uma função de destruição
não pode ter argumentos. Como um destrutor não tem argumentos
não se pode utilizar os mecanismos de “overloading”. A questão
dos argumentos por defeito, evidentemente, também não é aplicada
às funções de destruição.
Não podemos obter o endereço de uma função de destruição. Não
temos necessidade de aceder a uma função de destruição através
de um ponteiro porque a função é utilizada apenas para destruir
um objecto da classe.
Uma função de destruição tem de ser uma função membro não
estática da sua classe. Isto é, um destrutor não pode ser declarado
como const, volatile, ou static.
Uma função de destruição não pode ser herdada. Neste caso temos
o mesmo problema que acontece para os construtores. Se um
destrutor pudesse ser herdado, o correcto destrutor poderia não
ser executado.
Quando uma declaração de uma classe contém objectos membros
que são eles próprios objectos de outras classes, o corpo do destrutor
da classe (que contêm esses) é executado antes dos destrutores para
esses objectos contidos.
Download

ppt1