Murilo :P

C++, Computação, Programação, Web e afins :)

A importância de declarar os destrutores como virtual em C++

with 5 comments

Funções Virtuais

Depois de muito tempo sem postar algo aqui vou falar sobre um especificador do C++ que muitas vezes é esquecido: a keyword virtual.

Essa keyword é utilizada para dizer que queremos que faça a avaliação da chamada dessa função em run-time, não em compile-time como é feito por padrão.

Mas no que isso pode ajudar?

#include <iostream>

struct base
{
	void funcao()
	{
		std::cout << "chamada da base\n";
	} 
	
	virtual void vfuncao()
	{
		std::cout << "chamada da base\n";
	}
};

struct derivada: public base
{
	void funcao()
	{
		std::cout << "chamada da derivada\n";
	} 
	
	void vfuncao()
	{
		std::cout << "chamada da derivada\n";
	}
};

int main()
{
	base* pt = new derivada;
	
	//nao virtual
	pt->funcao();
	
	//virtual
	pt->vfuncao();
	
	delete pt;
	
	return 0;
}

Saída:

chamada da base
chamada da derivada

Ou seja, quando uma função-membro (aka. método) é declarada como virtual na classe base, a checagem em run-time é feita e é possível encontrar a vfuncao() na classe

derivada

, assim chamando a função correta, o que não acontece com funcao(): apesar de ter uma função-membro com mesmo nome na classe derivada, é chamada a funcao() da classe base porque o ponteiro que a referencia é do tipo da classe base. Pra isso serve a keyword virtual (êba, vamos usá-la daqui pra frente!).

Mas e os destrutores virtuais?

Segue a mesma idéia e com um agrave de risco eminente de memory leaks!
Se na classe base não temos o destrutor declarado como virtual, temos um grande problema. Vejamos o exemplo:

#include <iostream>

class base 
{
	int* ptr;
public:
	base()
	{
		ptr = new int;
	}
	
	~base() 
	{
		delete ptr;
		std::cout << "'destruindo' base\n";	
	}
};

class derivada: public base
{
	int* dptr;
public:
	derivada() {
		dptr = new int;
	}
	~derivada()
	{
		delete dptr;
		std::cout << "'destruindo' derivada\n";
	}
};

int main()
{
	base* pt = new derivada;
	delete pt;
}

Saída:

'destruindo' base

Como podemos ver, apenas o destrutor da classe base é chamado, deletando o ponteiro ptr. Mas e o dptr declarado em derivada e alocado em seu construtor? Leaked! Se perdeu!

A solução é declarar o destrutor da classe base como virtual e a mágica acontece.

#include <iostream>

class base 
{
	int* ptr;
public:
	base()
	{
		ptr = new int;
	}
	
	virtual ~base() 
	{
		delete ptr;
		std::cout << "'destruindo' base\n";	
	}
};

class derivada: public base
{
	int* dptr;
public:
	derivada() {
		dptr = new int;
	}
	~derivada()
	{
		delete dptr;
		std::cout << "'destruindo' derivada\n";
	}
};

int main()
{
	base* pt = new derivada;
	delete pt;
}

Saída:

'destruindo' derivada
'destruindo' base

Conclusão

É aconselhável que seus destrutores sejam virtuais e que pondere nas outras funções, pois tudo que deixa de ser feito em compile-time e passa a ser feito em run-time, gera overhead na sua aplicação (é… nem tudo é maravilha). Até a próxima!

Advertisements

Written by Murilo Adriano

12 de February de 2010 at 14:50

Posted in C/C++, Programação

Tagged with ,

5 Responses

Subscribe to comments with RSS.

  1. Você sabe que venho de um background mais alto nível, então perdoe minha dúvida caso não faça muito sentido em C++ =D

    Quando se faz uma classe é importante deixar a possibilidade de que ela seja estendida e que polimorfismo seja utilizado para ter o novo comportamente mesmo com as assinaturas antigas.

    Pensando nisso, não faz sentido que todos os métodos da minha classe sejam virtuais?

    Exceto, claro, os que especificamente queremos o contrário.

    Não sei se fui claro…

    garotosopa

    13 de February de 2010 at 08:45

    • Também andei pensando nisso Sopa, e cheguei a conversar com outras pessoas e chegamos a conclusão que polimorfismo é caro e que nem toda classe precisa ser polimórfica.
      É caro porque quando uma função é virtual, acontece o chamado Dynamic Binding para chamar a função-membro certa e isso é feito em run-time o que gera overhead (por criar uma Virtual Table ou vtable e ter derreferenciações a mais para cada chamada de função-membro).
      Outra coisa é que com os compiladores mais recentes, a capacidade de otimização em compile-time é muito grande então, ao deixar de fazer algo em compile-time perde-se esse potencial de otimização.

      Como C++ é uma linguagem de propósito bem geral, desempenho é muito levado em conta em alguns casos, porém num ambiente OO que não precisemos escovar bits em busca de otimização, apesar de não ter visto em lugar nenhum sobre isso, acho que seria realmente melhor declarar as funções-membro como virtual.

      Outra coisa, quanto temos algo do tipo:

      Base* pointer = new Derivada;

      e queremos chamar a função (até não-virtual) correta basta fazer um cast para o tipo correto:

      ((Derivada*)pointer)->funcao_nao_virtual();

      Porém isso não é bom porque não podemos afirmar por exemplo o tipo de retorno de funcao_nao_virtual() uma vez que uma função virtual garante:

      class base
      {
      virtual int function()
      {
      return 10;
      }
      };
      class derivada: public base
      {
      bool function()
      {
      return false;
      }
      };

      //saida do g++:
      tvirtual.cpp:14: erro: conflicting return type specified for ‘virtual bool derivada::function()’
      tvirtual.cpp:5: erro: overriding ‘virtual int base::function()’

      Abraço!

      Murilo Adriano

      18 de February de 2010 at 15:06

    • Garotosopa,

      O livro do Scott Meyers “Effective C++” tem um item dedicado ao tópico de utilização do “virtual” para destrutores. Na terceira edição do livro é o item 7 que trata desse assunto.
      Tirando Java, não sei como linguagens “mais alto nível” tratam disso, mas no C++ sempre que declara uma função virtual os objetos devem carregadas informações extras para que o runtime saiba como determinar quais funções virtuais devem ser invocadas no objeto.
      Essa informação extra é representada por um ponteiro chamado de vptr que apontam para uma tabela chamada vtbl.
      Pois bem, agora imagine que você vai criar uma classe pequena, como um Point2D para representar um par de coordenadas 2D (x e y). Caso você não use “virtual”, o tamanho em memória para essa classe é de 128 bits caso se use variáveis de 64 bits para x e y. Agora, se você usar “virtual” o tamanho em memória aumenta 50% pois será necessário um ponteiro de 64 bits a mais para cada objeto.
      Se pensarmos, mas são só 64 bits a mais de memória, isso não é nada. Não é nada para UM objeto, mas se tu tiver um array de 100 milhões de objetos, como em um jogo 3D ou uma aplicação científica, é coisa para caramba.
      Exite outra desvantagem de se usar “virtual” nesse caso. Imagine que você quer passar um array desses objetos para um programa compilado em outra linguagem que não utiliza esse esquema de montagem em memória, por exemplo Fortran. Você vai ter que fazer uns malabarismos para deixar seu código funcionando e ainda ser portável visto as especificidades do arranjo de memória feito por cada compilador/arquitetura/SO.

      Augusto César Righetto

      25 de February de 2010 at 01:02

  2. Murilo,

    Parabéns pelo blog, tem muita informação boa aqui. Cheguei aqui por um comentário teu no blog do garotosopa.

    Agora sobre o teu post, o livro do Scott Meyers “Effective C++” tem um item dedicado ao tópico de utilização do “virtual” para destrutores. Na terceira edição do livro é o item 7 que trata desse assunto.

    Resumidamente, deve-se declarar o “virtual” para todas as classes base polimórficas. No entanto, NÃO se deve declarar “virtual” para classes que não são projetadas para serem classes base ou de serem usadas polimórficamente.

    Augusto César Righetto

    25 de February de 2010 at 00:43


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: