A função Supports pode ser traiçoeira


Quando você se acostuma a utilizar somente instancias de interfaces, pode ser difícil entender alguns erros em tempo de execução, utilizando instâncias de classes com suporte a interfaces.

Unsplash image Photo by Michał Parzuchowski on Unsplash

Você pode desenvolver seu projeto do zero, utilizando apenas instâncias de interfaces, e (quase) tudo estará sob seu controle.

Entretanto, há muitos projetos onde só iremos fazer algum tipo de manutenção. Esses projetos podem não utilizar o conceito de interfaces como explicado em vários artigos desse blog. Nesses casos, é necessário codificar de forma que mantenha o código legado funcionando, ao mesmo tempo que introduzimos boas práticas de Orientação a Objetos.

Em tais projetos, é comum vermos variáveis declaradas como tipo de alguma classe ao invés de alguma interface.

Isso quer dizer que haverá chamadas aos métodos Free de cada instância, por todo o código.

E não há nada de errado com isso. Essa é uma das características da linguagem Object Pascal que a torna muito eficiente para projetos de alta performance.

No entanto, como já foi dito muitas vezes aqui nesse blog, o uso de instâncias de interfaces pode trazer muitos benefícios quando utilizada apropriadamente.

Os benefícios, entretanto, podem ser ofuscados pelo não funcionamento correto do código ao utilizarmos instâncias de classes junto com interfaces.

Veja o código abaixo e, antes de compilar e executar o programa no seu computador, tente descobrir se há algo errado:

program Project1;

uses
  SysUtils;

type
  IBar = interface
  ['{C22FB8F4-1EC6-42C4-81E4-F2A52CC52258}']
  end;

  TBar = class(TInterfacedObject, IBar)
  end;

var
  Bar: TBar;
begin
  Bar := TBar.Create;
  try
    if Supports(Bar, IBar) then
      Writeln('Bar supports...');
  finally
    Bar.Free;
  end;
end.

O código compila?

O código possui memory leaks?

O código irá lançar algum Access Violation (AV)?

Pense por um minuto…

Bem, eu posso lhe afirmar que o código compila sem erros mas há um grande problema de design.

A lógica geral está correta. Temos uma instância de classe chamada Bar; depois, checamos se essa instância suporta ou implementa a interface IBar utilizando a função Supports() da SysUtils; então, imprimimos algo na tela em caso afirmativo; finalmente, a instância deveria ser destruída utilizando Bar.Free, visto que é uma instância de classe e não de interface. Certo?

Errado.

Você irá receber um AV quando o compilador tentar destruir o objeto na linha Bar.Free.

E por quê isso ocorre?

Estamos utilizando uma das implementações da função Supports() — há alguns overloads — que retorna apenas um boolean para dizer ao programa se determinada instância suporta ou não uma determinada interface.

Ao fazer isso, Supports() irá obter uma instância de IBar, extraída da variável Bar. Isso irá alterar a contagem de referência e após o retorno da função, a instância Bar será destruída… antes de chegar na linha Bar.Free!

Na minha opinião, isso é um erro mesmo sendo by design.

Delphi e FPC funcionam da mesma forma.

Então, como consertar o código?

Basta alterar o tipo da variável para Bar: IBar. Ao fazer isso, o compilador irá lhe dizer que não é mais necessário chamar Bar.Free. Remova também essa linha e, consequentemente, a construção try-finally:

var
  Bar: IBar;
begin
  Bar := TBar.Create;
  if Supports(Bar, IBar) then
    Writeln('Bar supports...');
end.

Após fazer isso, compile e execute novamente o programa.

Utilizando FPC 3.0.4 com -gl habilitado, eu vejo a saída abaixo, sem nenhum memory leak ou erros:

c:\temp>project1.exe
Bar supports...
Heap dump by heaptrc unit
48 memory blocks allocated : 1189/1296
48 memory blocks freed     : 1189/1296
0 unfreed memory blocks : 0
True heap size : 163840 (80 used in System startup)
True free heap : 163760

Muito cuidado ao utilizar instâncias de classes com suporte a interfaces e contagem de referência.

Até logo.

Posts Relacionados

  • Memória Segura Utilizando Instâncias de Interfaces

  • Classes Mutáveis vs Objetos Imutáveis

  • Implementando Interfaces Utilizando Diferente Assinaturas de Métodos

  • Usando Paths ao invés de Diretivas de Compilação

  • Trabalhando com Exceções em Requisições HTTP

  • Tipo object Continua Vivo

  • Array de Objetos

  • Variáveis Locais Deveriam ter Nomes Curtos

  • Como Dividir e Organizar o Código em Formulários com Muitos Widgets

  • Pascal Deveria ser Modernizado?