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.
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.