• Nenhum resultado encontrado

Detalhes da herança Listas de inicialização dos construtores

O C++ oferece uma sintaxe alternativa para os construtores. Podemos construir um objecto utilizando uma lista de inicialização no construtor. A lista de inicialização do construtor substitui as expressões de atribuição do construtor. Exemplo:

class Loja { int num; double vendas; public:

Loja (int n, double v) : num(n), vendas(v) { }; // construtor com lista de inicialização ~Loja ( );

print ( ); };

É preciso preceder a lista de inicialização com :, inserindo esta lista entre a lista de argumentos do construtor e {.

De notar que não é preciso haver um corpo do construtor porque a lista de inicialização trata da inicialização dos membros.

Construtores e herança

Uma classe derivada tem que construir a sua classe base. Se o construtor da classe base requer um argumento ou mais, isto é, se requer mais do que um construtor por defeito, a classe derivada tem que fornecer o(s) argumento(s) chamando o construtor da classe base. Uma classe derivada tem que se preocupar apenas com a sua classe mãe imediata (a que está imediatamente acima dela), Se existe uma cadeia de classes derivadas, cada uma delas cuida da sua classe mãe imediata.

Para construir uma classe mãe, na classe derivada, é só adicionar uma lista de argumentos à função construtor da classe derivada. O C++ utilizará essa lista de argumentos extra para inicializar o construtor da classe base. Vamos ver um exemplo:

#include <iostream.h> #include <iomanip.h> #include <string.h> // classe base class Inventario { int quant, reorder; double preco; char * descricao; public:

Inventario (int q, int r, double p, char * d); ~Inventario ( );

void print ( );

int obtem_quant ( ) { return quant; }; int obtem_reorder ( ) { return reorder; }; double obtem_preco ( ) { return preco; }; };

Inventario::Inventario (int q, int r, double p, char * d) : quant (q), reorder (r), preco (p)

{

descricao = new char (strlen(d)+1); // deixa espaço para ‘\0’ strcpy (descricao, d);

}

{

delete descricao; }

void Inventario::print ( ) {

cout << “Descrição: “ << descricao << endl; }

// classe derivada

class Auto : public Inventario { char * negociante;

public:

Auto ( int, int, double, char * , char *); // construtor ~Auto ( );

void print ( );

char * obtem_negociante ( ) { return negociante; } };

Auto::Auto (int q, int r, double p, char * d, char * neg) : Inventario (q, r, p, d)

{

negociante = new char (strlen(neg)+1; strcpy (negociante, neg);

} Auto::Auto ( ) { delete negociante; } void Auto::print ( ) {

cout << setiosflags (ios::fixed);

cout << “Negociante: ”, << negociante << endl; }

void main ( ) {

Auto carro(3, 1, 8765.99, “4-portas”, “GM”; // …

}

É preciso garantir que a classe base é devidamente construída, porque se não for, a classe derivada nunca estará totalmente construída. A lista de argumentos na lista do construtor da classe base, na classe derivada, tem que estar na mesma ordem do que os argumentos do construtor da classe base.

NOTA: Se a classe derivada é derivada de uma classe base que é ela própria derivada de uma outra classe base, o C++ percorre toda a árvore da hierarquia, construindo a primeira classe base, e depois todas as classes derivadas que se sucedem, até que todos os objectos sejam criados. Se a classe A é a classe base para B, e a classe B é a classe base para C, quando se cria um objecto C, o C++ primeiro cria um objecto A, depois um objecto B e só então um C. Afinal, um filho não pode existir se os seus pais nunca existiram. Claro que é preciso fornecer o código apropriado para o construtor da classe base (tanto na classe base como na(s) derivada(s)).

Algumas considerações sobre o construtor da classe base

A classe base pode não necessitar de um construtor específico chamado a partir da classe derivada. aqui estão as possibilidades:

• Se o construtor da classe base requer argumentos, a classe derivada tem que chamar o construtor da classe base, fornecendo esses argumentos, como foi visto. Se a classe derivada “não cuida da sua

Elaborado por Ana Paula Costa mãe”, o C++ emite um erro porque não tem informação suficiente para construir o objecto da classe base.

• Se a classe base não necessita de um construtor, ou requer apenas um construtor por defeito, o C++ chama o construtor quando se define o objecto da classe derivada. Assim, não é preciso fazer nada com o construtor da classe base. As classes derivadas só precisam de se preocupar com a especificação do construtor da classe base quando o construtor base por defeito não é suficiente. Se a classe derivada não tem um construtor e a classe base requer um construtor, irá ocorrer um erro. Se a classe base necessita apenas de um construtor por defeito, ele será chamado automaticamente pelo C++ quando define o objecto da classe derivada.

Pode haver situações em que a classe derivada por si não necessita de um construtor, mas ele terá que existir apenas para chamar o construtor da classe base.

Não é preciso haver preocupações com as chamadas dos destruidores da classe base. O C++ chama automaticamente o destruidor da classe base (ou o destruidor por defeito se não existir nenhum) quando o objecto da classe derivada fica for a de escopo. Primeiro o C++ chama o destruidor da classe derivada e depois o destruidor da classe base (a ordem contrária às chamadas dos construtores).

Alterar o acesso dos membros herdados

class Abc { int I; protected: char * nome; public: float stuff; void print ( ); Abc get_vals ( ); Abc ( ); ~Abc ( ); }

class Xyz : protected Abc { // herda publico como protegido float adicional;

public: Xyz ( ); ~Xyz ( ); };

A função print ( ) em Xyz passou a ter acesso protegido. Se se pretendesse que print ( ) continuasse com acesso público na classe Xyz, podia-se fazê-lo da seguinte forma:

class Abc { int I; protected: char * nome; public: float stuff; void print ( ); Abc get_vals ( ); Abc ( ); ~Abc ( ); }

class Xyz : protected Abc { // herda publico como protegido float adicional;

public:

Abc::print; // assim, print passa a ser publico e não protegido Xyz ( );

~Xyz ( ); };

Limitações da herança – o que não pode ser herdado

• Funções construtores, incluindo construtores por cópia • Funções destruidores

• Funções friend

• Sobrecargas do operador new • Sobrecargas do operador = • Dados e funções membro estáticos

Herança múltipla

A herança múltipla permite que uma classe derivada herde propriedades e comportamentos de mais do que uma classe, ou seja, a classe pode ter mais do que uma classe base. Vamos ver um exemplo:

class Inventario {

// código da classe base // …

};

class Auto : public Inventario { // código da classe derivada Auto // …

};

class Maquina : public Inventario { // código da classe derivada Maquina // …

};

class OrdemCliente : public Auto, public Maquina {

// código da classe derivada OrdemCliente que tem herança múltipla // …

};

Deve-se evitar utilizar herança múltipla porque pode trazer alguns problemas de manutenção de erros.

Classe base virtual

Por vezes pode haver situações em que não se consiga evitar a herança múltipla. Esta secção explica como contornar uma situação de herança múltipla.

Quando se está numa situação em que uma classe base é partilhada por outras duas (ou mais) classes derivadas, não há qualquer problema, porque a herança singular continua a funcionar. Como no caso das classes Auto e Maquina que derivam de Inventario.

Um problema pode surgir quando se decide herdar uma nova classe a partir de ambas as classes derivadas (Auto e Maquina), é o caso da classe OrdemCliente. A figura seguinte mostra o resultado desta herança:

classe Inventario classe Inventario

classe Auto classe Maquina

Elaborado por Ana Paula Costa A classe OrdemCliente inclui duas cópias da classe base Inventario. Isto pode gerar conflitos entre as duas cópias da classe base. O que se deve fazer é partilhar a classe base, em vez de se usar a herança múltipla.

Para se partilhar uma classe base é só inserir a palavra reservada virtual na lista de acesso à classe base, nas classes derivadas. virtual assegura que não haverá uma nova cópia da classe base cada vez que a classe é herdada. A classe base virtual aparece apenas uma vez na hierarquia de herança. O exemplo de cima ficaria:

class Inventario {

// código da classe base // …

};

class Auto : virtual public Inventario { // código da classe derivada Auto // …

};

class Maquina : virtual public Inventario { // código da classe derivada Maquina // …

};

class OrdemCliente : public Auto, public Maquina {

// código da classe derivada OrdemCliente que tem herança múltipla // …

};

Agora quando se herdam as classes derivadas Auto, Maquina e OrdemCliente, vai existir apenas uma única cópia de Inventario.

O construtor da classe base também é preciso ter em consideração. Recordar que é preciso construir a classe base quando se constrói a classe derivada. Acrescentando os construtores ao exemplo:

class Inventario { int quant; public:

Inventario (int q) { quant = q; } };

class Auto : virtual public Inventario { char tipo;

public:

Auto (char t, int q) : Inventario (q) { tipo = t; } };

class Maquina : virtual public Inventario { float largura;

public:

Maquina (float l, int q) : Inventario (q) { largura = l; } };

class OrdemCliente : public Auto, public Maquina { int ordnum;

public:

OrdemCliente (int o, int q, char t, float l) :

ordnum (o), Inventario (q), Auto (t,q), Maquina (l,q) { }; };

Neste caso, como as classes derivadas estão a partilhar uma cópia da classe base, os construtores da classe base das classes derivadas do meio (Auto e Maquina) não irão funcionar quando se define o objecto da classe OrdemCliente.

O construtor de OrdemCliente constrói não só a ele próprio, mas também constrói a classe base e depois as suas classes derivadas. Isto vai contra a regra de que cada filho toma conta da sua classe mãe, mas as classes mãe de OrdemCliente não constróem a classe base porque não podem. A palavra reservada virtual diz a Auto e Maquina para não se preocuparem com a sua classe mãe quando o objecto OrdemCliente é criado, porque OrdemCliente terá que fazer todo o trabalho.

Se se definir um objecto Auto e outro Maquina: Auto carro (‘x’, 7);

Maquina xpto (23.23, 6);

Cada objecto chama o construtor da classe base. A palavra virtual não tem significado para estes objectos porque eles só iriam herdar uma única cópia de Inventario que qualquer das formas.

Se se definir um objecto OrdemCliente assim: OrdemCliente gaspar (12, 5, ‘F’, 6.3);

O construtor de gaspar trata de inicializar todas as classes que lhe estão hierarquicamente acima.

Na verdade não é preciso herança múltipla. Existem linguagens OO que não comportam herança múltipla (o Smalltalk, por exemplo). Herança simples é tudo o que é necessário.

Capítulo 14 – Como usar classes dentro de outras classes (compor uma

Documentos relacionados