Usando os Ponteiros de Objeto no MQL5
Introdução
No MQL5, você pode criar a sua própria classe para uso futuro de variáveis do tipo classes em seu código. Como já sabemos do artigo A Ordem de Criação e Destruição de Objetos em MQL5, as estruturas e classes podem ser criadas de duas maneiras - automaticamente e dinamicamente.
Para criar um objeto automaticamente, simplesmente declare uma variável de tipo classe - o sistema irá criá-lo e iniciá-lo automaticamente. Para criar um objeto dinâmico é necessário apontar o operador novo ao ponteiro do objeto de maneira explícita.
Entretanto, qual a diferença entre os objetos criados automaticamente e dinamicamente? Quando se faz necessário o uso do ponteiro para objeto e quando que a criação de objetos automaticamente já é suficiente? Estes tópicos serão os assuntos abordados neste artigo. Primeiramente, vamos discutir algumas possíveis armadilhas quando se trabalha com objetos e considerar maneiras de resolvê-los.
Um Erro Crítico ao Acessar um Ponteiro Inválido
Primeiramente você deve se lembrar que ao usar os ponteiros de objetos,é obrigatório a inicialização do objeto antes de seu uso. Quando você acessar um ponteiro inválido, o programa em MQL5 se encerrará com um erro crítico, assim, o programa é removido. Como exemplo, vamos considerar um Expert Advisor simples, com a classe CHello declarada logo abaixo. O ponteiro para a instância de classe é declarado em nível global.
//+------------------------------------------------------------------+ //| GetCriticalError.mq5 | //| Copyright 2010, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "2009, MetaQuotes Software Corp." #property link "http://www.mql5.com" #property version "1.00" //+------------------------------------------------------------------+ //| Uma Classe simples | //+------------------------------------------------------------------+ class CHello { private: string m_message; public: CHello(){m_message="Iniciando...";} string GetMessage(){return(m_message);} }; //--- CHello *pstatus; //+------------------------------------------------------------------+ //| Função de inicialização do Expert | //+------------------------------------------------------------------+ int OnInit() { //--- chamando um método para mostrar seu status Print(pstatus.GetMessage()); //--- Imprimindo uma mensagem se o Expert Advisor foi inicializado corretamente Print(__FUNCTION__," A função OnInit() foi concluída"); //--- return(0); } //+------------------------------------------------------------------+ //| Função tick do Expert | //+------------------------------------------------------------------+ void OnTick() { //--- } //+------------------------------------------------------------------+
A variável pstatus é o ponteiro do objeto, mas nós "esquecemos" intencionalmente de criar o próprio objeto usando o operador new. Se você tentar executar este Expert Advisor no gráfico EURUSD, você verá o resultado natural - o Expert Advisor será imediatamente descarregado no estágio de execução da função OnInit(). As mensagens que aparecem no Jornal Experts são as seguintes:
14:46:17 Expert GetCriticalError (EURUSD, H1) carregamento bem sucedido
14:46:18 Initializing of GetCriticalError (EURUSD, H1) falhou
14:46:18 Expert GetCriticalError (EURUSD, H1) removido
Este exemplo é muito simples, o erro capturado é simples. Entretanto, se o seu programa MQL5 contém centenas ou até milhares de linhas de códigos, a captura desses erros pode ser bem complicada. É importante, especialmente para os casos em que as condições de situações de emergência no comportamento do programa depende de fatores imprevisíveis - por exemplo, na estrutura de mercado em particular.
Verificação do Ponteiro Antes do seu Uso
Será que é possível evitar o encerramento crítico do programa? Sim, é claro! Basta inserir a verificação do ponteiro do objeto antes do seu uso. Vamos modificar este exemplo adicionando a função PrintStatus:
//+------------------------------------------------------------------+ //| Imprime uma mensagem usando um método de CHello do tipo objeto | //+------------------------------------------------------------------+ void PrintStatus(CHello *pobject) { if(CheckPointer(pobject)==POINTER_INVALID) Print(__FUNCTION__," a variável 'objeto' não foi inicializada!"); else Print(pobject.GetMessage()); }
Agora esta função chama o método GetMessage(), o ponteiro do objeto do tipo CHello é passado pela função. Primeiramente, ela verifica o ponteiro usando a função CheckPointer(). Vamos adicionar um parâmetro externo e salvar o código do Expert Advisor no arquivo GetCriticalError_OnDemand.mq5.
//+------------------------------------------------------------------+ //| GetCriticalError_OnDemand.mq5 | //| Copyright 2010, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "2009, MetaQuotes Software Corp." #property link "http://www.mql5.com" #property version "1.00" input bool GetStop=false;// para obter um erro crítico //+------------------------------------------------------------------+ //| Uma Classe simples | //+------------------------------------------------------------------+ class CHello { private: string m_message; public: CHello(){m_message="Iniciando...";} string GetMessage(){return(m_message);} }; //--- CHello *pstatus; //+------------------------------------------------------------------+ //| Imprime uma mensagem usando um método de CHello do tipo objeto | //+------------------------------------------------------------------+ void PrintStatus(CHello *pobject) { if(CheckPointer(pobject)==POINTER_INVALID) Print(__FUNCTION__," a variável 'objeto' não foi inicializada!"); else Print(pobject.GetMessage()); } //+------------------------------------------------------------------+ //| Função de inicialização do Expert | //+------------------------------------------------------------------+ int OnInit() { //--- chamando um método para mostrar seu status if(GetStop) pstatus.GetMessage(); else PrintStatus(pstatus); //--- Imprimindo uma mensagem se o Expert Advisor foi inicializado corretamente Print(__FUNCTION__," A função OnInit() foi concluída"); //--- return(0); } //+------------------------------------------------------------------+ //| Função tick do Expert | //+------------------------------------------------------------------+ void OnTick() { //--- } //+------------------------------------------------------------------+
Agora podemos executar o Expert Advisor de duas maneiras:
- Com um erro crítico (GetStop = true)
- Sem um erro, mas com a mensagem sobre o ponteiro inválido (GetStop = false)
Por padrão, a execução do Expert Advisor será bem sucedida e a seguinte mensagem aparecerá no Jornal "Experts":
GetCriticalError_OnDemand (EURUSD, H1) 15:01:57 PrintStatus a variável 'objeto' não foi inicializada!
GetCriticalError_OnDemand (EURUSD, H1) 15:01:57 OnInit function OnInit () is completed
Assim, a verificação do ponteiro antes do seu uso permite evitar os erros críticos.
Sempre verifique a exatidão do ponteiro antes do seu uso na função
Passando Objetos Não Inicializados por Referência
O que acontece se você passar objetos não inicializados como um parâmetro de entrada da função? (o próprio objeto por referência, não o ponteiro do objeto). Os objetos complexos, tais como classes e estruturas são passados por referência com um ampersand (|&). Vamos reescrever alguns códigos do GetCriticalError_OnDemand.mq5. Vamos renomear a função PrintStatus() e reescrever o seu código de maneira diferente.
//+------------------------------------------------------------------+ //| Imprime uma mensagem usando um método de CHello do tipo objeto | //+------------------------------------------------------------------+ void UnsafePrintStatus(CHello &object) { DebugBreak(); if(CheckPointer(GetPointer(object))==POINTER_INVALID) Print(__FUNCTION__," a variável 'objeto' não foi inicializada!"); else Print(object.GetMessage()); }
Agora, a diferença é que a própria variável deste tipo é passada por referência como um parâmetro de entrada em vez do ponteiro do objeto do tipo CClassHello. Vamos salvar a versão nova do Expert Advisor como GetCriticalError_Unsafe.mq5.
//+------------------------------------------------------------------+ //| GetCriticalError_Unsafe.mq5 | //| Copyright 2010, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "2009, MetaQuotes Software Corp." #property link "http://www.mql5.com" #property version "1.00" input bool GetStop=false;// para obter um erro crítico //+------------------------------------------------------------------+ //| Uma Classe simples | //+------------------------------------------------------------------+ class CHello { private: string m_message; public: CHello(){m_message="Iniciando...";} string GetMessage(){return(m_message);} }; //--- CHello *pstatus; //+------------------------------------------------------------------+ //| Imprime uma mensagem usando um método de CHello do tipo objeto | //+------------------------------------------------------------------+ void UnsafePrintStatus(CHello &object) { DebugBreak(); if(CheckPointer(GetPointer(object))==POINTER_INVALID) Print(__FUNCTION__," a variável 'objeto' não foi inicializada!"); else Print(object.GetMessage()); } //+------------------------------------------------------------------+ //| Função de inicialização do Expert | //+------------------------------------------------------------------+ int OnInit() { //--- chamando um método para mostrar seu status if(GetStop) pstatus.GetMessage(); else UnsafePrintStatus(pstatus); //--- Imprimindo uma mensagem se o Expert Advisor foi inicializado corretamente Print(__FUNCTION__," A função OnInit() foi concluída"); //--- return(0); } //+------------------------------------------------------------------+ //| Função tick do Expert | //+------------------------------------------------------------------+ void OnTick() { //--- } //+------------------------------------------------------------------+
Pode-se então ver a diferença entre os Expert Advisors GetCriticalError_OnDemand.mq5 e GetCriticalError_Unsafe.mq5 sobre a maneira em que o parâmetro é passado pela função. Para o primeiro caso o ponteiro do objeto é passado para a função e para o segundo caso o próprio objeto é passado por referência. Em ambos os casos, antes do uso do objeto e seu ponteiro, a função verifica se o ponteiro está correto.
Isso significa que os Expert Advisors trabalham da mesma maneira? Não, não! Vamos executar o Expert Advisor com o parâmetro GetStop=false e novamente teremos um erro crítico. O fato é que se um objeto é passado por referência o erro crítico ocorrerá no estágio de chamada da função, devido ao objeto não iniciado ter sido passado como um parâmetro. Você pode verificá-lo, se você executar o script em modo de depuração diretamente do MetaEditor5, usando o botão F5
Para evitar os pontos de interrupção manuais, vamos modificar a função adicionando o ponto de interrupção DebugBreak () dentro da função.
//+------------------------------------------------------------------+ //| Imprime uma mensagem usando um método de CHello do tipo objeto | //+------------------------------------------------------------------+ void UnsafePrintStatus(CHello &object) { DebugBreak(); if(CheckPointer(GetPointer(object))==POINTER_INVALID) Print(__FUNCTION__," a variável 'objeto' não foi inicializada!"); else Print(object.GetMessage()); }
No modo de depuração, o Expert Advisor será descarregado antes da chamada da função DebugBreak() . Como escrever um código seguro para o caso se um objeto não iniciado for passado por referência? A resposta é simples - usar a sobrecarga de funções.
Se um ponteiro de um objeto não iniciado for passado por referência como um parâmetro da função, ele levará a um erro crítico e irá encerrar o programa mql5.
Usando a sobrecarga de funções para um código mais seguro
Seria muito inconveniente, se o desenvolvedor, que usa uma biblioteca externa, forçasse executar a verificação dos objetos de entrada para a exatidão. É muito melhor executar todas as verificações necessárias dentro da biblioteca, isto pode ser implementado usando a sobrecarga de funções.
Vamos implementar o método UnsafePrintStatus() usando a sobrecarga de funções e escrever duas versões desta função - a primeira, que usa o ponteiro do objeto passado em vez do próprio objeto, a segunda, que usa a passagem do objeto por referência. Ambas as funções terão o mesmo nome "PrintStatus", mas esta implementação não será mais potencialmente perigosa.
//+--------------------------------------------------------------------------------------+ //| A impressão de modo seguro de uma mensagem usando o ponteiro do objeto de CHello | //+--------------------------------------------------------------------------------------+ void SafePrintStatus(CHello *pobject) { if(CheckPointer(pobject)==POINTER_INVALID) Print(__FUNCTION__," a variável 'objeto' não foi inicializada!"); else Print(pobject.GetMessage()); } //+-------------------------------------------------------------------------+ //| Imprimindo uma mensagem usando o objeto CHello, passado por referência | //+-------------------------------------------------------------------------+ void SafePrintStatus(CHello &pobject) { DebugBreak(); SafePrintStatus(GetPointer(pobject)); }
Agora para o caso de a função ser chamada passando pelo ponteiro do objeto, a verificação de sua exatidão será executada e o erro crítico não ocorrerá. Se você chamar a sobrecarga de função na passagem de um objeto por referência, primeiramente, conseguiremos o ponteiro do objeto usando a função GetPointer e então chamamos o código mais seguro, que usa a passagem de um objeto pelo ponteiro.
Podemos reescrever uma versão segura da função ainda mais curta, ao invés de
void SafePrintStatus (CHello & pobject) ( DebugBreak (); CHello * p = GetPointer (pobject); SafePrintStatus (p); )
vamos escrever a versão compacta:
void SafePrintStatus (CHello & object) ( DebugBreak (); SafePrintStatus (GetPointer (object)); )
Ambas as versões são iguais. Vamos salvar a segunda versão com o nome GetCriticalError_Safe.mq5.
//+------------------------------------------------------------------+ //| GetCriticalError_Safe.mq5 | //| Copyright 2010, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "2009, MetaQuotes Software Corp." #property link "http://www.mql5.com" #property version "1.00" input bool GetStop=false;// para obter um erro crítico //+--------------------------------------------------------------------+ //| Uma Classe simples | //+--------------------------------------------------------------------+ class CHello { private: string m_message; public: CHello(){m_message="Iniciando...";} string GetMessage(){return(m_message);} }; //--- CHello *pstatus; //+--------------------------------------------------------------------------------------+ //| A impressão de modo seguro de uma mensagem usando o ponteiro do objeto de CHello | //+--------------------------------------------------------------------------------------+ void SafePrintStatus(CHello *pobject) { if(CheckPointer(pobject)==POINTER_INVALID) Print(__FUNCTION__," a variável 'objeto' não foi inicializada!"); else Print(pobject.GetMessage()); } //+-------------------------------------------------------------------------+ //| Imprimindo uma mensagem usando o objeto CHello, passado por referência | //+-------------------------------------------------------------------------+ void SafePrintStatus(CHello &pobject) { DebugBreak(); SafePrintStatus(GetPointer(pobject)); } //+------------------------------------------------------------------+ //| Função de inicialização do Expert | //+------------------------------------------------------------------+ int OnInit() { //--- chamando um método para mostrar seu status if(GetStop) pstatus.GetMessage(); else SafePrintStatus(pstatus); //--- Imprimindo uma mensagem se o Expert Advisor foi inicializado corretamente Print(__FUNCTION__," A função OnInit() foi concluída"); //--- return(0); } //+------------------------------------------------------------------+ //| Função tick do Expert | //+------------------------------------------------------------------+ void OnTick() { //--- } //+------------------------------------------------------------------+
Se você usar duas funções com implementações diferentes (passando por referência e passando pelo ponteiro do objeto), isto lhe permite assegurar um trabalho seguro da sobrecarga de funções.
Finalmente, aprendemos como usar estes objetos, passados pela função como parâmetros, agora é hora de aprender:
Quando Você Precisa de Ponteiros?
Os ponteiros de objeto permitem executar um gerenciamento flexível no processo de criação e destruição de objetos e permite que você crie objetos abstratos mais complexos. Deixando o programa mais flexível.
Lista Encadeada
Em alguns casos uma maneira diferente de organização de dados é necessária, a lista encadeada é uma delas. A classe de uma lista encadeada CList está disponível na Biblioteca Padrão, aqui apresentaremos os nossos próprios exemplos. Uma lista encadeada significa que cada item da lista está conectado com o próximo item (next) e o item anterior (prev), se eles existirem. Para organizar tais ligações, é conveniente usar os ponteiros de objeto dos itens da lista (ListItem).
Vamos criar uma classe que representará um item da lista, como este:
class CListItem { private: int m_ID; CListItem *m_next; CListItem *m_prev; public: ~CListItem(); void setID(int id){m_ID=id;} int getID(){return(m_ID);} void next(CListItem *item){m_next=item;} void prev(CListItem *item){m_prev=item;} CListItem* next(){return(m_next);} CListItem* prev(){return(m_prev);} };
A própria lista será organizada em uma classe separada:
//+------------------------------------------------------------------+ //| Lista Encadeada | //+------------------------------------------------------------------+ class CList { private: int m_counter; CListItem *m_first; public: CList(){m_counter=0;} ~CList(); void addItem(CListItem *item); int size(){return(m_counter);} };
A classe CList contém um ponteiro do primeiro item da lista m_first , o acesso aos outros itens da lista estará sempre disponível através das funções next() e prev() da classe CListItem(). A classe CList tem duas funções interessantes. A primeira é a função para adicionar um item novo na lista.
//+------------------------------------------------------------------+ //| Adicionando um ítem à lista | //+------------------------------------------------------------------+ CList::addItem(CListItem *item) { //--- verificação de um ponteiro, ele deve estar correto if(CheckPointer(item)==POINTER_INVALID) return; //--- aumentando o número de itens da lista m_counter++; //--- Verificando se não há elementos na lista if(CheckPointer(m_first)!=POINTER_DYNAMIC) { m_first=item; } else { //--- Definindo para primeiro o ponteiro do intem anterior m_first.prev(item); //--- salvando um ponteiro do primeiro item atual CListItem *p=m_first; //--- colocando o item de entrada no lugar do primeiro elemento m_first=item; //--- definindo um ponteiro para o próximo item no primeiro item na lista m_first.next(p); } }
Para cada novo item, adicionado à lista, ele se torna o primeiro item, o ponteiro do primeiro item anterior é gravado no campo m_next. Então, um item, adicionado primeiro à lista estará no final da lista. O último item adicionado será o primeiro e o seu ponteiro estará gravado na variável m_first. A segunda função interessante é o destruidor ~CList(). Ele é chamado quando o objeto é destruído, ele deve assegurar a destruição correta dos objetos da lista. Ele pode ser feito de maneira muito simples:
//+------------------------------------------------------------------+ //| Destruidor da lista | //+------------------------------------------------------------------+ CList::~CList(void) { int ID=m_first.getID(); if(CheckPointer(m_first)==POINTER_DYNAMIC) delete(m_first); Print(__FUNCTION__," o primeiro item com ID =", ID, "é destruído"); }
Pode-se ver que, se m_first contém o ponteiro correto do item da lista, somente o primeiro item da lista será removido. Todos os outros itens da lista são removidos em avalanche, pois o destruidor de classe CListItem() em funcionamento produz a desinicialização correta do objeto.
//+------------------------------------------------------------------+ //| Destruidor de itens | //+------------------------------------------------------------------+ CListItem::~CListItem(void) { if(CheckPointer(m_next)==POINTER_DYNAMIC) { delete(m_next); Print(__FUNCTION__," removendo um item com ID =", m_ID); } else Print(__FUNCTION__," o próximo item não foi definido para o item com o ID =", m_ID); }
A exatidão de um ponteiro é verificada dentro do destruidor, se ele for especificado, o objeto com o ponteiro m_next será destruído usando o operador delete(). Antes da destruição, o primeiro item da lista chama o destruidor, ele irá excluir o segundo item, então ele causará a exclusão do terceiro item e assim por diante até o fim da cadeia.
O trabalho da lista é exibido no script SampleList.mq5. A declaração de uma lista (uma variável de CListType) está na função OnStart(). Esta lista será criada e inicializada automaticamente. O preenchimento da lista é executado dentro de uma lista e primeiramente, cada item da lista é criado dinamicamente usando o operador new e então adicionado à lista.
void OnStart() { //--- CList list; for(int i=0;i<7;i++) { CListItem *item=new CListItem; item.setID(i); list.addItem(item); } Print("Há ",list.size()," itens na lista"); }
Execute o script e você verá as seguintes mensagens no jornal "Experts".
2010.03.18 11:22:05 SampleList (EURUSD, H1) CList:: ~ CList O primeiro item com ID=6 será destruído
2010.03.18 11:22:05 SampleList (EURUSD, H1) CListItem:: ~ CListItem Removendo um item com ID = 6
2010.03.18 11:22:05 SampleList (EURUSD, H1) CListItem:: ~ CListItem Removendo um item com ID = 5
2010.03.18 11:22:05 SampleList (EURUSD, H1) CListItem:: ~ CListItem Removendo um item com ID = 4
2010.03.18 11:22:05 SampleList (EURUSD, H1) CListItem:: ~ CListItem Removendo um item com ID = 3
2010.03.18 11:22:05 SampleList (EURUSD, H1) CListItem:: ~ CListItem Removendo um item com ID = 2
2010.03.18 11:22:05 SampleList (EURUSD, H1) CListItem:: ~ CListItem Removendo um item com ID = 1
2010.03.18 11:22:05 SampleList (EURUSD, H1) CListItem:: ~ CListItem O próximo item não foi definido com o itemID=0
2010.03.18 11:22:05 SampleList (EURUSD, H1) Há 7 itens na lista
Se você estudou o código cuidadosamente, não será uma surpresa que o item com ID=0 não tenha o próximo item seguinte. Entretanto, nós consideramos somente uma razão, quando o uso dos ponteiros faz com que a escrita dos programas seja conveniente e legível. Há uma segunda razão e é chamada de
Polimorfismo
É frequentemente necessário implementar a mesma funcionalidade para objetos diferentes, pertencentes ao mesmo tipo. Por exemplo, há objetos simples como linha, triângulo, retângulo e círculo. Apesar de eles parecerem diferentes eles têm uma característica em comum - eles podem ser desenhados. Vamos criar uma classe base CShape e usá-la para a criação de seu descendentes para cada tipo de formas geométricas.
Para fins educacionais, a classe de base e seus descendentes tem uma funcionalidade mínima.
//+------------------------------------------------------------------+ //| A classe base para a forma do objeto | //+------------------------------------------------------------------+ class CShape { private: int m_type; public: CShape(){m_type=0;} void Draw(); string getTypeName(){return("Forma");} }; //+------------------------------------------------------------------+ //| A classe do objeto linha | //+------------------------------------------------------------------+ class CLine:public CShape { private: int m_type; public: CLine(){m_type=1;} void Draw(); string getTypeName(){return("Linha");} }; //+------------------------------------------------------------------+ //| A classe do objeto triângulo | //+------------------------------------------------------------------+ class CTriangle:public CShape { private: int m_type; public: CTriangle(){m_type=2;} void Draw(); string getTypeName(){return("Triangle");} }; //+------------------------------------------------------------------+ //| A classe do objeto retângulo | //+------------------------------------------------------------------+ class CRectangle:public CShape { private: int m_type; public: CRectangle(){m_type=3;} void Draw(); string getTypeName(){return("Retângulo");} }; //+------------------------------------------------------------------+ //| A classe do objeto círculo | //+------------------------------------------------------------------+ class CCircle:public CShape { private: int m_type; public: CCircle(){m_type=4;} void Draw(); string getTypeName(){return("Círculo");} };
A classe principal CShape contém duas funções, que são sobreescritas manualmente em seus descendentes - a Draw() e getTypeName(). A função Draw() se destina a desenhar uma forma e a função getTypeName() retorna uma descrição em texto da forma.
Vamos criar um banco de dados *shapes[], que contenha ponteiros do tipo base CShape e especificar os valores de um ponteiro para as classes diferentes.
//+------------------------------------------------------------------+ //| Função início do Script | //+------------------------------------------------------------------+ void OnStart() { //--- um array de ponteiros de objetos do tipo CShape CShape *shapes[]; //--- redimensionamento de um array ArrayResize(shapes,5); //--- Preenchendo os ponteiros do array shapes[0]=new CShape; shapes[1]=new CLine; shapes[2]=new CTriangle; shapes[3]=new CRectangle; shapes[4]=new CCircle; //--- imprimindo o tipo de cada elemento do array for(int i=0;i<5;i++) { Print(i,shapes[i].getTypeName()); } //--- excluindo todos os objetos do array for(int i=0;i<5;i++) delete(shapes[i]); }
A classe principal CShape contém duas funções, que são sobreescritas manualmente em seus descendentes - a Draw() e getTypeName(). A função Draw() se destina a desenhar uma forma e a função getTypeName() retorna uma descrição em texto da forma.
2010.03.18 14:06:18 DemoPolymorphism (EURUSD, H1) 4 Forma
2010.03.18 14:06:18 DemoPolymorphism (EURUSD, H1) 3 Forma
2010.03.18 14:06:18 DemoPolymorphism (EURUSD, H1) 2 Forma
2010.03.18 14:06:18 DemoPolymorphism (EURUSD, H1) 1 Forma
2010.03.18 14:06:18 DemoPolymorphism (EURUSD, H1) 0 Forma
A explicação deste fato é a seguinte: o banco de dados *shapes[] é declarado como um banco de dados de ponteiros do tipo CShape e então cada objeto do banco de dados chama o método getTypeName() de uma classe base, mesmo que um descendente tenha uma implementação diferente. Para chamar a função getTypeName() correspondente com o tipo de objeto real (descendente) no momento da execução do programa, é necessário declarar esta função em uma classe base como uma função virtual.
Vamos adicionar a palavra-chave virtual à função getTypeName() na declaração da classe principal CShape.
class CShape ( private: int m_type; public: CShape () (m_type = 0;) void Draw (); virtual string getTypeName () (return ("Forma");) )
e executar o script novamente. Agora os resultados são consistentes com aqueles esperados:
2010.03.18 15:01:11 DemoPolymorphism (EURUSD, H1) 4 Círculo
2010.03.18 15:01:11 DemoPolymorphism (EURUSD, H1) 3 Retângulo
2010.03.18 15:01:11 DemoPolymorphism (EURUSD, H1) 2 Triângulo
2010.03.18 15:01:11 DemoPolymorphism (EURUSD, H1) 1 Linha
2010.03.18 15:01:11 DemoPolymorphism (EURUSD, H1) 0 Forma
Então, a declaração de uma função virtual em uma classe base permitiu a chamada da mesma função de um descendente durante a execução do programa. Agora podemos implementar a função Draw() com recurso total para cada uma das classes derivadas.
Um exemplo deste trabalho pode ser encontrado no script anexado DrawManyObjects.mq5, que mostram formas aleatórias no gráfico.
Conclusão
Então, é hora de resumir. Em MQL5, a criação e destruição dos objetos é executada automaticamente, então você deve usar os ponteiros somente se eles forem realmente necessários e se você souber como trabalhar com eles.
Entretanto, se você não puder fazer sem a utilização de ponteiros, certifique-se de verificar a exatidão do ponteiro antes do seu uso, usando o CheckPointer() - foi adicionado especialmente para esses casos.
Uma última coisa: no MQL5 os ponteiros não são na verdade ponteiros de memória, como os usados em C++, então você não deve passá-los para DLL como parâmetros de entrada.
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/36
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso