[Solucionado] problema com conversao dinamica (dynamic_cast) C++

11 respostas
DavidUser
estava tentando usar o dynamic_cast e me deparei com um erro que para mim nao fez muito sentido.
/*
 * Account.h
 *
 *  Created on: 05/07/2011
 *      Author: david
 *
 * Classe generica de conta
 */

#ifndef ACCOUNT_H_
#define ACCOUNT_H_
#include <iostream>

class Account {
private:
	double amount; //saldo da conta
public:
	explicit Account(double); //incializa saldo da conta
	void credit(double); //credita valor na conta
	bool debit(double); //debita valor na conta
	double getBalance() const; //pega o saldo atual da conta
};

#endif /* ACCOUNT_H_ */
/*
 * SavingAccount.h
 *
 *  Created on: 06/07/2011
 *      Author: david
 */

#ifndef SAVINGACCOUNT_H_
#define SAVINGACCOUNT_H_
#include "Account.h"

class SavingAccount : public Account {
private:
	float rate;
public:
	SavingAccount(double,float); // saldo inicial da conta, percentual de juros da poupanca
	double calculateInterest();
};

#endif /* SAVINGACCOUNT_H_ */
[size=18]Este é o meu arquivo principal onde tento a conversão na linha 36:[/size]
/*
 * main.cpp
 *
 *  Created on: 28/07/2011
 *      Author: david
 */

#include <typeinfo>
#include <vector>
using std::vector;
#include <iostream>
using std::cin;
using std::cout;
using std::endl;

#include "hierarquia_account/SavingAccount.h"
#include "hierarquia_account/CheckingAccount.h"

int main() {
	vector<Account *> contas(2);

	SavingAccount contaPoupanca(0,0.4); //saldo inicial 0 e 0.4% de juros
	CheckingAccount contaCorrente(0, 0.3); //saldo inicial 0 e 30 centavos de taxa por transacao

	contas[0] = &contaPoupanca;
	contas[1] = &contaCorrente;

	for (int i = 0, valor; i < contas.size(); i++) {
		cout << "Informe um valor a ser debitado na conta " << i + 1 << ": ";
		cin >> valor;
		contas[i]->debit(valor);
		cout << "Informe um valor a ser creditado na conta " << i + 1 << ": ";
		cin >> valor;
		contas[i]->credit(valor);

		SavingAccount* contaPoupanca = dynamic_cast<SavingAccount*>(contas[i]);

		if (contaPoupanca)
			contaPoupanca->credit(contaPoupanca->calculateInterest());
		cout << "Saldo da conta: " << contas[i]->getBalance() << " reais.\n\n";
	}
}

11 Respostas

DavidUser

descobri que o dynamic_cast só pode ser feito se tiver o caso de polimorfismo entre a classe básica e a classe derivada para qual vai ser convertida, mas gostaria de uma explicação do porque de não poder fazer a conversão se não houver polimorfismo ou seja métodos virtual na hierarquia.

DavidUser

Entendi que o dynamic_cast só pode ser aplicado se existir o polimorfismo e que o próprio destrutor virtual declarado na classe básica resolve tal requisito, que se compreendi é porque ao se utilizar o dynamic_cast ele faz algum tipo de consulta nas vtables (tabelas virtuais com ponteiros de função definidas para cada classe polimórfica) para converter o tipo de ponteiro, estou certo? Alguém pode me explicar melhor?

ViniGodoy

O que acontece é o seguinte. O dynamic_cast foi criado para garantir que a operação de cast é segura.

É bastante fácil identificar se um cast é feito do filho para o pai. Como o pai faz parte da declaração do filho, o compilador sabe que essa é sempre uma situação válida e pode fazer o cast. Agora, a situação inversa não é verdadeira. Considere:

Esse é um cast feito em Runtime. É impossível saber se o objeto retornado é mesmo do tipo Filho ou não (ele poderia ser do tipo Filho2, ou simplesmente do tipo Pai, o que tornaria o cast inviável). O cast, para testar isso, terá que se basear em algum tipo de informação.
É válido lembrar aqui, que o C++ não tem qualquer tipo de informação de reflexão associada a objetos, tal como o Java. Então, nesse caso, ele precisará se basear em algum outro tipo de informação que ele tenha.

A maior parte das implementações usa então o seguinte artifício: Ela vai até a vtable da classe, e testa se essa vtable é compatível com a da classe filha em questão. Se for, então a conversão de tipos será válida. Entretanto, para que esse artifício funcione é necessário existir uma vtable, o que só ocorre com um método virtual (e, no mínimo, um destrutor virtual deve existir, caso contrário, é um erro de programação fazer herança sobre a classe pois você pode criar memory leaks no seu programa).

Note que o comportamento do dynamic cast é dependente de implementação. Um compilador pode, por exemplo, funcionar para qualquer tipo de classe (virtual ou não) caso a opção de RTTI esteja ligada: inclusive a recomendação geral é que ela sempre esteja, se você faz operações desse tipo. Ele então pode se basear no RTTI e não só na vtable para fazer a comparação. Alguns compiladores das antigas usavam comparações ainda mais lentas e sinistras, baseadas em tabelas de strings, para fazer o dynamic_cast funcionar.

Seu código está bastante interessante. Está lendo o Effective C++?

DavidUser

Muito Obrigado pela explicação Vini!
Ainda estou lendo o Deitel, mas me parece interessante o título “Effective C++” qual o autor?

ViniGodoy

Veja aqui o Roadmap para sair de larva para virar o ninja dos nove cortes do C++:
http://www.pontov.com.br/site/index.php/cpp/46-conceitos-basicos/88-roadmap-c

O autor é o Scott Meyers. Você pode comprar o livro pela Amazon.

E

Em particular, cuidado com ponteiros.

Quando você toma um endereço para uma variável LOCAL e o armazena em um vetor (por exemplo), você pode acabar sendo ferroado por um “dangling pointer”.

Normalmente, quando quero programar em C++ como se programa em Java (ou seja, sem me preocupar com “dangling pointers” e vazamentos de memória, eu uso algum tipo de smart pointer, como o boost::shared_ptr<> ou então o std::shared_ptr do C++0X.

vector<Account *> contas(2);  
   
     SavingAccount contaPoupanca(0,0.4); //saldo inicial 0 e 0.4% de juros  
     CheckingAccount contaCorrente(0, 0.3); //saldo inicial 0 e 30 centavos de taxa por transacao  
   
     contas[0] = &contaPoupanca;  
     contas[1] = &contaCorrente;
DavidUser

Não terei problemas com “dangling pointer” pois os ponteiros para variáveis locais que criei também são de escopo local.

Ponteiros são uma das maiores vantagens da programação C/C++ os problemas com ponteiros não são por causa da linguagem mais pelo uso incorreto dessa característica.

E

Sei. Um problema que costuma ocorrer é basicamente de manutenção. Quando é só você que mantém seu programa, você de alguma forma sabe que tal ponteiro para uma variável local só será usado em escopo local (ou por um método que não armazena esse ponteiro em uma estrutura de dados não-local)
Uma vez que você se afasta um pouco desse programa (ou então que outra pessoa começa a mexer no programa) está aberta a lata de minhocas, e problemas esquisitos começam a aparecer.

DavidUser

Basicamente se você coloca o código na mão de alguém que não tem real conhecimento do que está fazendo problemas vão aparecer, concordo plenamente.

Creio que quando um código é bem documentado, mesmo fugindo de padrões, para ganho de desempenho ele pode ter uma boa manutenção desde que esteja em mãos competentes que entenda as reais necessidades do projeto.

E

De qualquer forma fica a dica: cuidado ao armazenar referências a variáveis locais em estruturas de dados quaisquer.
Mesmo você enchendo seu programa de comentários, uma hora você acaba passando essa estrutura de dados a um método que não respeita alguma das pressuposições que você fez, e você acaba tendo problemas (do tipo "mas meu programa funciona bem em modo debug, por que é que ele não funciona em modo release?).
O caso clássico é quando você passa um endereço desses para um método que pode executar ou não em outra thread. Então…

DavidUser

Realmente interessante o caso das threads entanglement, muito obrigado!

Bem que meu professor diz para tentar ao máximo utilizar serviços das classes evitando passar ponteiros ou o caso de referências oscilantes, principalmente quando não se pode contar mais com a execução em modo sequencial e único (como conceito de arquitetura de von Neumann).

Criado 28 de julho de 2011
Ultima resposta 29 de jul. de 2011
Respostas 11
Participantes 3