Post: [Generics] Ponteiros Inteligentes

Alessandro Medeiros

Fala ai Radizeios e Radizeiras, tudo bem com vocês?

Mas um post da série de generics, e esse é o último post dessa nossa série.

Não fique triste, pois aqui no blog você ainda irá ter diversos conteúdos sobre Delphi.

Você já imaginou, nunca mais precisar de se preocupar em criar um objeto e destruir esse objeto?

Simplesmente escrever o nome do objeto, o código dele, e já sair utilizando, e deixasse que todo o resto a linguagem fizesse para você?

Não estou falando de utilizar interfaces, como já conhecemos na programação orientada a objeto.

Como já mostrei diversas vezes aqui no blog.

Tem uma forma muito legal utilizando generics, onde não precisamos mais ficar criando objetos, usar try finally.

Iremos aprender aqui no post de hoje sobre Ponteiros Inteligentes.

Iremos criar alguns ponteiros, que vão ser responsáveis por criar os objetos para nós, e destruir esses objetos, sem que precisemos ficar nos preocupando com isso.

Isso irá reduzir muitas linhas de códigos.

E é o que eu costo dizer, “programador precisa ser criativo”.

Para que você consiga ganhar, vamos dizer assim, em criatividade, você precisa conhecer os recursos da linguagem, e os generics é um dos recursos que facilita nossa vida.

Separei esse conteúdo muito especial, justamente para esse nosso último momento da série sobre generics.

Vamos então aprender sobre Ponteiros Inteligentes?

Vamos lá?

Bom, como de costume, não será um exemplo pronto, mas já tenho um projeto em branco VCL.

E primeira coisa que iremos fazer é colocar um botão nesse nosso formulário.

Dentro desse botão iremos simular, por exemplo, a criação de um StringList.

procedure TForm1.Button1Click(Sender: TObject);
var
SL : TStringlist;
begin
SL := TStringlist.Create;
try
SL.Add('Teste');
ShowMessage('Total de Regsitro: ' + IntToStr(SL.Count));
finaly
SL.Free;
end;
end;

Observe que criamos uma variável do tipo TStringList.

Observe que na implementação da utilização do TStringList eu precisei instância-la e destruí-la, usando o try…finally.

Nada de novo, não é verdade?

A utilização do try…finally, é para que não venhamos ter vazamento de memória.

Mas para que você possa entender melhor do que estou falando, dentro do Create deste formulário irei colocar o ReportMemoryLeaksOnShutdown.

procedure TForm2.FormCreate(Sender: TObject);
begin
ReportMemoryLeaksOnShutdown := True;
end;

Foi adicionado esse comando dentro do create para que, se houver algum vazamento de memória, ele possa identificar.

Observe que executamos o projeto, clicamos no botão, recebemos a mensagem, fechamos o projeto,  e tudo funcionando perfeitamente, não acontece nada, perfeito né?

Então, agora irei comentar o Free, onde é destruído o StringList, e ao executar novamente.

Observe que após fechar o projeto ele me retornou o memory leak.

Isso acontece porque o objeto foi criado, mas não foi destruído.

A ideia da utilização de ponteiros inteligentes, é para que não tenha a necessidade de ter que escrever todo o código que escrevemos na criação de um objeto, como o StringList.

Ponteiros Inteligentes nos dá a possibilidade de ir direto ao ponto.

Então vamos criar uma nova classe chamada SmartPoint.

type
TSmartPoint<T : class> = record
strict private
FValue : T;
funciton GetValue : T;
public
constructor Create(aValue : T);
property Value : T read GetValue;
...
{TSmartPoint}
constructor TSmartPoint<T>.Create(aValue : T);
begin
FValue := aValue;
end;
function TSmartPoint<T>.GetValue : T;
begin
Result := FValue;
end;

Observe que dentro dessa classe criamos um record, que recebe um objeto genérico, que trabalha somente com classe, pois somente as classe podemos instanciar e destruir, outros tipos não.

Com essa classe eu possuo agora uma estrutura para o ponteiro inteligente.

Quando for criar, por exemplo, um StringList, basta chamar essa classe de SmartPoint e passar para ela o tipo genérico StringList, desta forma, passo a ter acesso aos métodos do StringList e essa nossa classe irá gerenciar tudo para nós.

Temos que ter bastante atenção, pois este post é um pouco avançado, se você está começando a programar poderá ter algumas dificuldades, mas não se preocupe, irei tentar explicar nos mínimos detalhes para que ao término desse post você possa aplicar no seu dia a dia.

Dentro dessa mesma unit, irei criar uma classe que irá fazer todo o gerenciamento.

...
TFreeTheValue = class(TInterfacedObject)
private
FObjectToFree : TObject;
public
constructor Create(anObjectToFree: TObject);
destructor Destroy; override;
public

Quando um objeto é criado, ele precisa ser destruído, o Delphi não faz essa limpeza de memórias.

Mas o Delphi possui o ARC (Automatic Reference Count), ele faz uma contagem de memória, onde verifica se aquele objeto está sendo utilizado, quando aquele objeto não está sendo mais utilizado, ele automaticamente destrói.

Só que ele só faz esse procedimento quando trabalhamos com interfaces.

Nem todo mundo gosta de trabalhar com interfaces, ou sabe trabalhar com interfaces.

Então o que vamos fazer? 

Iremos utilizar de forma implícita, oculta dentro de nossa classe.

Vamos colocar uma interface simples, que irá utilizar o ARC, ficará transparente para quem estiver trabalhando.

Dentro dessa nossa unit eu criei a classe TFreeTheValue, uma classe para limpar os valore.

Ela herda de TInterfacedObject, então acaba herdando os atributos das interfaces, criei uma variável do tipo TObject, ao criar essa nossa classe possa ser passado os objetos a ele.

A criação da classe é bem simples, mas observe o que foi feito nas implementações desses métodos constructor e destructor.

constructor TFreeTheValue.Create(anObjectToFree: TObject);
begin
FObjectToFree := anObjectToFree;
end;
destructor TfreeThevalue.Destroy;
begin
FObjectToFree.Free;
inherited;
end;

Simplesmente eu passei para a variável o objeto que é passado no create, e dentro do destroy eu retiro da memória.

Isso foi feito para que, quando essa classe estiver sendo utilizada, ela entra no escopo, o contador de referência irá somar algo para ela, quando ela sair do escopo, o contador de referência irá chamar o destroy dela.

Essa classe TFreeTheValue será utilizada agora dentro da classe TSmartPoint.

Quando ela for referenciada e não ter mais referência, ou seja, criada e destruída, ela estará criando e limpando isso para nós.

Agora iremos criar dois métodos que irão fazer toda a diferença na utilização.

Iremos utilizar o class operator, que são operadores de classes dentro do Delphi.

Mas o que é isso?

Não irei me aprofundar muito nas class operator, mas essas class operator, permite que possamos criar operadores para as classes, como o próprio nome diz.

Por exemplo, quando você pega 2 inteiros, onde uma variável A que é um inteiro e a variável B, que também é um inteiro, ao colocar o operador de soma, como por exemplo, A + B, ou seja, inteiro mais inteiro, ele vai lá dentro desse operador de soma (+) e faz a operação de somar esses dois números e dar o resultado.

Quando você cria suas classes, você também pode criar seus operadores.

Esse operadores faz com que ao passar um objeto ele vai direto para esse operador e executa a ação que lhe foi proposta.

Dentro dessa nossa classe eu simplesmente estou utilizando o operador implicit, como pode ser visto dentro do código abaixo.

...
class operator Implicit(smart: TSmartPoint<T>);
class operator Implicit(AValue : T) : TSmartPoint<T>;
...

Esse operador ele pega o SmartPoint e seta para ele, como por exemplo, um string, e ao ser utilizado ele irá aguardar um String, como mostra no código abaixo.

...
class operator Implicit(a : String);
...
TSmartPoit := 'Thulio';

Nesse caso, isso não daria certo, porque TSmartPoint é uma classe, mas isso não daria certo se não tivesse o class operator, como eu tenho esse operador, o compilador irá deixar isso acontecer.

Quando o compilador chegar na linha que está sendo passado um valor string para a classe, nesse momento o compilado chama esse método dá class operator, que está recebendo uma string, e dentro desse método eu trabalho o que quiser.

É mais ou menos isso sobre as class operator, em breve irei postar um artigo sobre class operator, para que você possa utilizar.

Então o que eu estou fazendo dentro da minha classe de SmartPoint, é criar dois operadores implícitos.

Onde um desses operadores implícitos recebe o genérico, quando for setado a classe TSmartPoint, ele irá chamar o método implicit que recebe o genérico.

E tem o outro implícito que recebe a própria classe com o genérico, e retorna o genérico.

Então eu possuo dois métodos implícitos dentro do TSmartPOint.

Agora veja como que implementamos esses métodos.

...
class operator TSmartPoint<T>.Implicit(smart: TSmartPoint&lt;T&gt;);
begin
Result := smart.value;
end;
class operator TSmartPoint<T>.Implicit(AValue : T) : TSmartPoint<T>;
begin
Result := TSmartPoint<T>.Create(AValue);
end;
...

Observe que o operador implícito recebe a própria classe, e simplesmente retorna o smart.value.

Esse smart.value que é retornado é a property que implementamos, e ele recebe já um objeto instanciado.

E o segundo método recebe um objeto para ser instanciado, então por este motivo que passamos para o Result a criação da classe passando o aValue para o create.

Até o momento está tudo pronto para que possamos trabalhar.

Agora vamos fazer com que a classe TFreeTheValue funcione.

Para que ela possa funcionar iremos criar uma variável FFreeTheValue dentro da classe TSmartPoint do tipo da interface IInterface.

FFreeTheValue: IInterface;
...
constructor TSmartPoint<T>.Create(aValue: T);
begin
FValue := aValue;
FFreeTheValue := TFreeTheValue.Create(FValue);
end;
...

Observe que dentro do create da classe TSmartPoint eu instancio essa variável, onde toda vez que essa classe receber o valor passado para aValue, é chamado a class TFreeTheValue passando aValue.

Então ela irá criar uma referência de memória para esse objeto que estou querendo criar.

Por exemplo, passei para o value um TStringList, e no primeiro momento eu seto para uma variável esse TStringList, até o momento não tem gerenciamento de ARC, então é criada uma variável que instancia a classe de Free.

No create da classe TFreeTheValue é setado o objeto que é passado no ato da criação, e quando ele for destruído irá limpar esse objeto.

constructor TFreeTheValue.Create(anObjectToFree: TObject);
begin
FObjectToFree := anObjectToFree;
end;
destructor TfreeThevalue.Destroy;
begin
FObjectToFree.Free;
inherited;
end;

Então eu só estou apontando a referência para ele lá na memória.

Neste momento o contador de referência já irá saber o que fazer para nós.

Fazendo todo essas implementações, usando generics ele já irá funcionar para nós.

Agora você irá ver como é economizado muitas linhas de código.

Vamos recapitular?

Primeira coisa que foi feito.

Criei uma classe Record, que irá ser o auxiliar para criar as minhas classes, sempre irei utilizá-la para criar as classes, para que não precise ficar gerenciando memória.

TSmartPoint<T : class> = record
strict private
FValue : T;
FFreeTheValue : IIterface;
function GetValue : T;
public
class operator Implicit(smart: TSmartPoint<T>) : T;
class operator Implicit(AValue : T) : TSmartPoint<T>;
constructor Create(aValue: T);
property Value: T read GetValue;
end;
constructor TSmartPoint<T>.Create(aValue: T);
begin
FValue := aValue;
FFreeTheValue := TFreeTheValue.Create(FValue);
end;

Observe que essa classe recebe um atributo genérico que é uma classe.

Quando ele receber esse atributo, ele irá chamar o create, para criar o objeto, e ao criar o objeto ele passa a variável para uma outra classe que usa interface para gerenciar o ARC, que é a TFreeTheValue.

TFreeTheValue = class(TInterfacedObject)
private
FObjectToFree: TObject;
public
constructor Create(anObjectToFree: TObject);
desctructor Destroy; overrride;
end;

Observe que nessa classe ele só seta o objeto, e quando ele é destruído, ou seja, quando ele perde a referência na memória, ele chama o destrói dessa classe, que limpa esse objeto.

Agora na classe de TSmartPoint eu tenho duas class operator, que são dois implícitos, para poder utilizar isso lá na minha camada principal, graças a esses implícitos eu vou poder trabalhar de forma mais limpa no meu código.

Agora que você irá ver como isso ficaria na nossa camada de visão, ou seja, no formulário.

Lembra que logo no início do post eu implementei um TStringList dentro de uma botão?

Então, vamos substituir todo esse código.

procedure TForm2.Button1Click(Sender: TObject);
var
SL : TStringList;
begin
SL := TStringList.Create;
try
SL.Add('Teste');
ShowMessage('Total de Registro: ' + IntToString(Sl.Count));
finally
SL.Free;
end;
end;

 Agora aonde eu estou setando o TStringList, eu vou passar a usar sempre a classe TSmartPoint para declarar as minhas classes.

procedure TForm2.Button1Click(Sender: TObject);
var
SL : TSmartPoint<TStringList>;
begin
SL := TStringList.Create;
SL.Value.Add('Teste');
ShowMessage('Total de Registro: ' + IntToString(Sl.Value.Count));
end;

Observe que a variável agora recebe o tipo da classe, mas com um atributo genérico TStringList.

Essa variável é um TStringList, sim ele é, mas eu estou usando um ponteiro inteligente.

Estamos usando um camarada para gerenciar tudo pra mim.

Em seguida você precisa instanciar o TStringList, isso mesmo que você leu, você precisa instanciar o TStringList.

Mas o que vai acontecer, o StringList é do tipo SmartPoint, mas estou instanciando TStringList, vai dar erro? Não.

Não dará erro, por que o implicit ao receber um atributo do tipo genérico, irá acionar o implícito que recebe um genérico.

...
class operator Implict(AValue: T) : TSmartPoint<T>;
...
class operator TSMartPoint<T>.Implict(AValue: T) : TSmartPoint<T>;
begin
Result := TSmartPoint<T>.Create(AValue);
end;

Viu o que é feito dentro desse implicit?

Ele cria o objeto, para que possa ser criada a referência para ele.

Por isso, não dá erro quando instanciamos o objeto, como no código anterior.

E viu que não precisamos mais utilizar o try… finally, onde acabei economizando algumas linhas de código, mas viu que no Add e Count, eu chamei antes o Value, ele que dá acesso ao objeto de nossa lista.

Dessa forma o código ficou bem mais limpo, e eu não preciso ficar me preocupando com gerenciamento de memória.

Vamos rodar o projeto para ver como irá se comportar?

Observe que simplesmente executar normalmente e ao fechar nada de vazamento de memória.

Isso tudo graças ao TSmartPoint, que faz todo o gerenciamento de memória para nós.

Agora se eu disser para você, que nem desse create, que utilizamos para instanciar o TStringList, você vai precisar?

Isso mesmo, você nem precisa criar ou destruir sua classe, você só irá precisar utilizar um pouquinho da sua criatividade, conhecer as ferramentas dentro do que você trabalhar.

Então deixa eu te mostrar.

Irei retirar esse create que temos no código, e você irá trabalhar com ele sem criar e sem destruir.

procedure TForm2.Button1Click(Sender: TObject);
var
SL : TSmartPoint<TStringList>;
begin
SL.Value.Add('Teste');
ShowMessage('Total de Registro: ' + IntToString(Sl.Value.Count));
end;

Isso mesmo que você viu, ele irá funcionar perfeitamente.

Esse meu TSmartPoint é um record, ele não precisa ser criado, pegando ele você já pode utilizar.

Depois que eu passo para variável, e ao chamar o Add, eu antes chama um Value, que retorna o objeto genérico.

Então basta eu fazer uma verificação dentro desse meu Value se o objeto está instanciado ou não, se não tiver eu crio ele.

Dentro da classe TSmartPoint, no método GetValue, onde simplesmente eu retorno o objeto, eu crio essa verificação.

function TSmartPoint<T>.GetValue: T;
begin
if not Assigned(FFreeTheValue) then
Self := TSmartPoint<T>.Create(T.Create);
Result := FValue;
end;

Lembrando que no create ele é passado para a variável FFreeTheValue.

Observe que verifico se ele não está instanciado, aí é chamada a própria instância dele, onde é chamado esse create, que irá setar o Value e o FFreeTheValue com a interface.

Mas se eu compilar esse código irá dar erro, porque o objeto genérico não tem um construtor para trabalhar, e você já aprendeu isso aqui no blog.

O que iremos fazer, vamos dentro da própria classe TSmartPoint e passo para ele o constructor.

TSmartPoint<T : class, constructor>

Agora ele possui um constructor, dessa forma agora já vai compilar.

Dentro do código não temos mas a instância do objeto da StringList, somente o trabalho de usar o objeto através do TSmartPoint, já podemos compilar e executar as operações tranquilamente.

Viu que executamos o projeto realizando as operações, que foi simplesmente clicar no botão, e fechamos o formulário e não ocorreu vazamento de memória.

E nosso projeto funcionando redondinho.

Muito legal né pessoal?

Tivemos um pouquinho de trabalho, mas nada tão complicado, quando você conhece todo o processo.

Espero que você tenha curtido essa nossa série de generics, cada post foi criado com todo carinho e atenção para que pudesse esmiuçar cada detalhes sobre generics, onde você consiga absorver o máximo.

E como eu tenho buscado muito para que pudesse levar conteúdos de valores para vocês, esse não podia ficar de fora.

Esse é um conteúdo do Marco Cantu, um dos grandes nomes do Delphi, ele que implementou esse conceito de SmartPoint, ponteiro inteligentes.

Então, tem um mundo para se explorar dentro da linguagem do Object Pascal, tem muita coisa legal.

Vai afundo, estuda, se dedica, tire horas por dia, se dedicando para estudar, para você ser bom, para você o melhor naquilo que você faz, tenha esse desejo dentro de você.

E assim como os conteúdos desta série, que encontra-se dentro do CLUBE DE PROGRAMADORES EM DELPHI, ele foi feito para lhe ajudar a alavancar nos estudos, onde você encontra uma gama de conteúdos que irá alavancar na sua carreira.

E caso você tenha interesse de conhecer mais sobre generics acessa o nosso portal do CLUBE DE PROGRAMADORES EM DELPHI, onde você não só terá conteúdos relacionados aos generics, mas uma quantidade enorme de conteúdos que poderá lhe ajudar muito no seu dia a dia, é uma verdadeira NETFLIX para os programadores Delphi.

CLIQUE AQUI E SAIBA MAIS SOBRE O CLUBE DOS PROGRAMADORES DELPHI




Faça sua busca

CATEGORIAS

POSTS RECENTES

E caso você tem interesse de conhecer mais sobre [Generics] Ponteiros Inteligentes, acesse o nosso portal do CLUBE DE PROGRAMADORES EM DELPHI
Você não terá só conteúdos relacionados ao [Generics] Ponteiros Inteligentes, mas uma quantidade enorme de conteúdos que poderá lhe ajudar muito no seu dia a dia, é uma verdadeira NETFLIX para os programadores Delphi.
Gostou?
Compartilhe:

Embarque no foguete com milhares de devs para aprender desenvolvimento, evoluir de forma contínua e se manter relevante no mercado.

Dúvidas
Cadastre-se em nossa lista

Para ter acesso em primeira mão, a tudo que acontece na Academia do Código, basta se cadastrar em nossa lista

Grupo Thulio Bittencourt | Academia do Código

#FaçaPartedaHistória

Copyright © 2024 – Todos os direitos reservados