preview
Do básico ao intermediário: Sobrecarga de operadores (II)

Do básico ao intermediário: Sobrecarga de operadores (II)

MetaTrader 5Exemplos |
53 0
CODE X
CODE X

Introdução

No artigo anterior Do básico ao intermediário: Sobrecarga de operadores (I), começamos a falar sobre como implementar o que é conhecido como sobrecarga de operadores. Porém aquele artigo teve como objetivo, apenas e tão somente iniciar a apresentação de um dos conceitos, que para iniciantes é um dos mais confusos que existe. Isto por que dependendo da maneira como a sobrecarga de operadores venha a ser feita. Um mesmo código, pode vir a ser mais legível, ou muito mais confuso. Isto pelo simples fato de que o programador, não se atentou ao fato de que a sobrecarga de operadores deve ser feita, justamente para tornar o código mais legível e simples de entender.

Porém aquele artigo foi apenas uma breve e deliciosa apresentação do que realmente podemos fazer. E como este assunto é extremamente divertido e muito interessante. Tanto pelo ponto de vista prático, quanto também pelo ponto de vista teórico. Quero apresentar as coisas de maneira a mais lúdica e fácil de entender quanto for possível ser feito. E isto sem entrar no assunto que será tratado em outros quatro artigos, que serão publicados no futuro. Onde exploro uma outra forma de se trabalhar com sobrecarga de operadores, voltados a um tipo de aplicação bem específica. Mas isto é outra história. Então fique ligados em todos os meus artigos. Pois esta outra forma de se trabalhar com a sobrecarga, é realmente muito prática e fácil de entender. Mas não irei abordar este sistema aqui, nesta série de artigos. Ok, então vamos iniciar o que será o tópico principal deste artigo.


Sobrecarga de operadores (II)

Muito bem, talvez uma das coisas mais importante dentro da programação, venha a ser a depuração de um código. Seja via IDE, como seria o caso do MetaEditor, ou quem sabe diretamente via arquivo log. Em alguns casos mais simples, podemos usar o próprio ToolBox de mensagens do terminal do MetaTrader 5. A forma de depurar o código, pouco importa. Porém saber como podemos fazer isto, pode nos ajudar a encontrar falhas e erros, que de outra maneira seriam muito difíceis de serem encontrados.

Ok, se você prestou atenção no artigo anterior, deve ter notado, quem mais no final daquele mesmo artigo, fiz uma pequena brincadeira, em um dos códigos fontes presentes ali. Aquela brincadeira visava justamente demonstrar algo que aqui iremos tratar com um pouco mais de profundidade. Isto por que, pode ser algo que venha a ser interessante, conforme você vier a estudar e desejar praticar a sobrecarga de operadores. Assim como quero manter as coisas o mais simples e didáticas, quanto for possível fazer. Vamos começar este artigo, revendo um dos códigos mostrado no artigo anterior. E partiremos dali para o que realmente quero demonstrar. O código em questão pode ser observado logo abaixo, na íntegra.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. struct stComplex
05. {
06. //+----------------+
07.     private :
08.         double m_r, m_i;
09. //+----------------+
10.     public  :
11. //+----------------+
12.         stComplex(): m_r(0), m_i(0) {}
13. //+----------------+
14.         stComplex(double r, double i): m_r(r), m_i(i) {}
15. //+----------------+
16.         stComplex operator+(const stComplex &arg1)
17.         {
18.             return stComplex(m_r + arg1.m_r, m_i + arg1.m_i);
19.         }
20. //+----------------+
21.         stComplex operator+(const double arg2)
22.         {
23.             return stComplex(m_r + arg2, m_i);
24.         }
25. //+----------------+
26.         stComplex operator+=(const double arg2)
27.         {
28.             return stComplex(m_r += arg2, m_i);
29.         }
30. //+----------------+
31.         void Debug(void)
32.         {
33.             PrintFormat("Internal Value = %.02f %c %.02fi", m_r, (m_i < 0 ? '-' : '+'), MathAbs(m_i));
34.         }
35. //+----------------+
36. };
37. //+------------------------------------------------------------------+
38. void OnStart(void)
39. {
40.     stComplex   a(2, 5),
41.                 b(8, -3),
42.                 c;
43. 
44.     c = a + b;
45.     c.Debug();
46. 
47.     (c += 4).Debug();
48.     c = b + 4;
49.     c.Debug();
50. }
51. //+------------------------------------------------------------------+

Código 01

Certo. Agora quero que você se desfaça ou se afaste de qualquer coisa que venha a lhe distrair, para que você consiga entender o que iremos fazer. Pois, irei tentar explicar uma coisa aqui, que faz muita gente perder as estribeiras quando o assunto é depuração de código. Mas iremos começar com uma implementação mais simples e fácil de entender. Depois irei mostra, uma coisa que beira o absurdo, mas que pode acontecer, caso você esteja efetuando a depuração de um código que esteja utilizando a sobrecarga de operadores.

Muito bem, a brincadeira foi feita na linha 47. Pergunta: Por que esta linha 47, do código 01, funciona? Resposta: Por que ela não é exatamente aquilo que você está vendo. Novamente, entender o que foi explicado em um artigo é muito importante para entender todos os demais. Esta linha 47, vista no código 01, é na verdade, interpretada pelo compilador conforme mostrado no fragmento de código visto logo abaixo.

                  .
                  .
                  .
    c.operator+=(4).Debug();
                  .
                  .
                  .

Fragmento 01

Agora olhando este fragmento 01, esta mesma linha 47, no código 01, faz todo o sentido, do por que ela funciona e consegue imprimir algo no terminal. Não é mesmo? Mas quero lhe mostrar como ir um pouco além disto, que está sendo feito na linha 47. Para isto, vamos fazer uma pequena mudança no código 01. Como a mudança é simples e direta, vamos promover ela em um fragmento. Já que não é necessário replicar todo o código na íntegra, para mostrar a mudança que será feita.

                   .
                   .
                   .
30. //+----------------+
31.         void Debug(uint arg)
32.         {
33.             PrintFormat("Debugging the line %d = %.02f %c %.02fi", arg, m_r, (m_i < 0 ? '-' : '+'), MathAbs(m_i));
34.         }
35. //+----------------+
36. };
37. //+------------------------------------------------------------------+
38. void OnStart(void)
39. {
40.     stComplex   a(2, 5),
41.                 b(8, -3),
42.                 c;
43. 
44.     c = a + b;
45.     c.Debug(__LINE__);
46. 
47.     (c += 4).Debug(__LINE__);
48.     c = b + 4;
49.     c.Debug(__LINE__);
50. }
51. //+------------------------------------------------------------------+

Fragmento 02

Observe o seguinte, este fragmento 02, mostra o que mudamos no código 01, a fim de conseguir produzir um depurador um pouco mais adequado, para o objetivo que pretendo demonstrar. Note que adicionei a capacidade do procedimento Debug, presente na linha 31, de receber um valor. Este valor irá indicar em que linha do código a chamada ocorreu. Par isto, observe que adicionamos um pequeno detalhe nas linhas que irão imprimir algo no terminal. Assim, quando este novo código 01, com estas mudanças vista no fragmento 02, for executado, teremos como resultado o que é visto na imagem logo abaixo.

Imagem 01

Ou seja, agora temos uma forma bem interessante de começar a fazer as coisas. Perfeito. Agora quero que a depuração, não aconteça diretamente nas linhas 45 e 49. Quero que elas aconteçam nas linhas 44 e 48. Pergunta como podemos fazer isto acontecer? Hum, isto parece ser algo muito difícil e extremamente complicado. Já quem em ambas as linhas estamos atribuindo um valor a uma variável. Então não vejo como poderíamos fazer isto.

Pois bem, meu caro leitor, novamente você está olhando as coisas de uma maneira. Quando na verdade, as deveria estar olhando de outra maneira, totalmente fora da caixa. Quero que você olhe estas mesmas linhas 44 e 48, e pense nelas como algo parecido com o que é visto no fragmento 01. Então lhe pergunto novamente. Como poderíamos depurar as linhas 44 e 48, imprimindo algo no terminal do MetaTrader 5?

Bem, existem duas formas de se fazer isto. Uma que, praticamente já temos em mãos e outra que precisaríamos implementar. A maneira como você deseja e irá fazer isto, depende de quanto esforço você está disposto a aplicar na implementação do código. Mas antes de mostrar como fazer para depurar ambas as linhas. Vamos primeiro ver como o compilador as estaria interpretando. Primeiro, com base no próprio conteúdo visto no código 01. Tais linhas seriam vistas pelo compilador da seguinte maneira.

                   .
                   .
                   .
    c = a.operator+(b);
                   .
                   .
                   .
    c = b.operator+(4);
                   .
                   .
                   .

Fragmento 03

Aqui no fragmento 03, podemos observar o que o compilador estaria vendo. Hum, interessante. Então pensando bem, tudo que precisamos fazer seria imaginar, este fragmento 03 se transformando em algo parecido com o fragmento 01, e teríamos a solução. Seria está a resposta? Sim, meu caro leitor. Agora você está começando a progredir. Mas tem um detalhe, em vez de escrever isto que vemos no fragmento 03, e adicionar o que vemos no fragmento 01. Isto a fim de criar a linha de depuração. Podemos fazer isto de uma forma um pouco melhor. Para isto, bastará modificar tanto a linha 44, como a linha 48, por algo um pouco diferente, que é visto no fragmento logo abaixo. Mas isto irá gerar um outro detalhe, que irá impedir do código ser compilado. Mas não se preocupe, irei explicar como corrigir este pequeno problema.

                   .
                   .
                   .
37. //+------------------------------------------------------------------+
38. void OnStart(void)
39. {
40.     stComplex   a(2, 5),
41.                 b(8, -3),
42.                 c;
43. 
44.     c = (a + b).Debug(__LINE__);
45.     c.Debug(__LINE__);
46. 
47.     (c += 4).Debug(__LINE__);
48.     c = (b + 4).Debug(__LINE__);
49.     c.Debug(__LINE__);
50. }
51. //+------------------------------------------------------------------+

Fragmento 04

Apesar de a ideia ser basicamente isto que estamos vendo no fragmento. Você sem pensar muito, já logo percebe que a coisa talvez não venha a funcionar. E isto devido a um pequeno detalhe no código 01. Então se você tentar compilar, o que agora será o nosso novo código 01. Depois de ter feito estas mudanças mostradas no fragmento 04. Irá ver o compilador emitir alguns erros bem estranhos. E estes podem ser visto na imagem logo abaixo.

Imagem 02

Estes erros mostrados nesta imagem 02, são por conta que a chamada usada para depurar a linha NÃO É UMA FUNÇÃO, MAS SIM UM PROCEDIMENTO. Por conta disto, tais erros são reportados pelo compilador. Resolver isto é algo muito simples e direto. Desde que você saiba como fazer. Já que não basta simplesmente ir na linha 31 do código e mudar ela de procedimento para uma função. Pois não é bem assim que a banda toca. Então para resolver o problema, primeiro você precisa entender que precisamos retornar alguma referência da nossa própria classe. Lembre-se de que isto implica em retornar algo como o que é visto nas linhas 23 e 28.

Portanto é necessário que você entenda como este fragmento 04 será visto pelo compilador. Sendo assim, podemos adotar duas soluções bem básicas. A primeira é vista logo abaixo.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. struct stComplex
05. {
06. //+----------------+
07.     private :
08.         double m_r, m_i;
09. //+----------------+
10.     public  :
11. //+----------------+
12.         stComplex(): m_r(0), m_i(0) {}
13. //+----------------+
14.         stComplex(double r, double i): m_r(r), m_i(i) {}
15. //+----------------+
16.         stComplex operator+(const stComplex &arg1)
17.         {
18.             return stComplex(m_r + arg1.m_r, m_i + arg1.m_i);
19.         }
20. //+----------------+
21.         stComplex operator+(const double arg2)
22.         {
23.             return stComplex(m_r + arg2, m_i);
24.         }
25. //+----------------+
26.         stComplex operator+=(const double arg2)
27.         {
28.             return stComplex(m_r += arg2, m_i);
29.         }
30. //+----------------+
31.         stComplex Debug(uint arg)
32.         {
33.             PrintFormat("Debugging the line %d = %.02f %c %.02fi", arg, m_r, (m_i < 0 ? '-' : '+'), MathAbs(m_i));
34.             return stComplex(m_r, m_i);
35.         }
36. //+----------------+
37. };
38. //+------------------------------------------------------------------+
39. void OnStart(void)
40. {
41.     stComplex   a(2, 5),
42.                 b(8, -3),
43.                 c;
44. 
45.     c = (a + b).Debug(__LINE__);
46.     c.Debug(__LINE__);
47.     (c += 4).Debug(__LINE__);
48.     c = (b + 4).Debug(__LINE__);
49.     c.Debug(__LINE__);
50. }
51. //+------------------------------------------------------------------+

Código 02

Observe que agora na linha 31 mudamos como as coisas funcionam. Desta forma agora iremos ter como fazer com que retornemos um valor que poderá ser atribuído a variável c. Isto olhando as linhas 45 e 48. Note que para permitir isto, adicionamos a linha 34, que tem como objetivo, justamente criar um valor que será retornado depois que a função de depuração fizer o seu trabalho. Com isto, ao executarmos este código 02, iremos ter como resultado o que é visto na imagem logo abaixo.

Imagem 03

Isto sim foi algo bem interessante. Mas podemos fazer ainda melhor. Isto por que, quando esta linha 34 do código 02, é executada. Estamos na verdade recriando estrutura, via o código de construção implementado na linha 14. Porém, na prática, dificilmente você verá algo assim sendo feito, já que isto é algo completamente desnecessário. O que de fato acontece na prática, é utilizarmos um outro operador para nos referir a uma estrutura já criada. E como até este momento, ainda não utilizarmos ele, chegou a hora de você o conhecer. Então dê um olá para o operador this.

Este operador this, substitui justamente está tipo de linha de código que estamos vendo na linha 34 do código 02. Porém, e esta é a parte importante. Esta linha 34 vista no código 02, está de fato criando uma nova instancia da estrutura que estamos retornando. Quanto o operador this, retorna a instancia da estrutura que estaremos utilizando. Sei que isto parece um tanto quanto confuso. Mas na prática é bem mais simples. Tanto que o código 02 precisa sofrer apenas e somente uma única mudança. E esta é vista no fragmento logo abaixo.

                   .
                   .
                   .
30. //+----------------+
31.         stComplex Debug(uint arg)
32.         {
33.             PrintFormat("Debugging the line %d = %.02f %c %.02fi", arg, m_r, (m_i < 0 ? '-' : '+'), MathAbs(m_i));
34.             return this;
35.         }
36. //+----------------+
                   .
                   .
                   .

Fragmento 05

Ok, acho que entendi como proceder. Mas estou com uma dúvida um tanto quanto cruel. Você informou que o operador this, irá retornar a instancia que estamos utilizando, dentro de uma estrutura. Por conta disto, utilizar o que é visto no fragmento 05, seria o mais adequado. Porém, e esta é justamente a minha dúvida, não vejo diferença entre usar o que é visto na linha 34 do código 02, e usar o que é visto na mesma linha 34 do fragmento 05. Já que no final, o resultado apresentado estará sendo exatamente o mesmo.

Sim, meu caro leitor, o resultado é exatamente o mesmo, porém os endereços não. Quando você utilizar o que é visto na linha 34 do código 02. Estará na verdade, criando uma nova instancia da própria estrutura que já estará sendo utilizada. E dependendo do tipo de coisa, que você estiver implementando, o fato de criar uma nova instancia, PODERÁ, e que este poderá fique bem destacado, criar um tipo de fatoração cujo resultado é completamente diferente do que você esperava encontrar. Justamente por ter criado uma nova instância da estrutura. Lembre-se do seguinte: Como programador, você JAMAIS cria algo, cujo resultado você não consegue predizer. Isto por que, não faz o mínimo sentido, programar algo que você não sabe se a resposta está correta ou errada. No entanto, ao usar o operador this, como é visto no fragmento 05, na mesma linha 34. Você NÃO ESTARÁ CRIANDO, aquilo que seria uma nova instância da estrutura. Você estará sim utilizando a própria instância já criada a fim de utilizar o que ela já contém em suas variáveis.

Como foi mencionado, este tipo de coisa é um tanto quanto confuso de se entender. No entanto, não estou aqui apenas para falar como as coisas funcionam, ou como deveriam ser pensadas. Estou aqui, para lhe mostrar como elas realmente funcionam por debaixo dos panos. E para entender esta questão sobre usar ou não o operador this no código. Precisaremos ver o que mencionei com relação as instancias de código. E para fazer isto, vamos utilizar o código, que seja o mais simples, quanto for possível ser criado. Assim acredito que ficará definitivamente muito mais claro, como uma simples escolha pode vir a influenciar todo o seu código. Lembrando mais uma vez, que isto pode ou não vir a ocorrer. Dependendo é claro do que você esteja implementando. Para cumprir este objetivo, iremos utilizar o código visto logo abaixo.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. class C_Demo
05. {
06.     public  :
07. //+----------------+
08.         C_Demo *Check_1(void)
09.         {
10.             return GetPointer(this);
11.         }
12. //+----------------+
13.         C_Demo *Check_2(void)
14.         {
15.             return GetPointer(C_Demo());
16.         }
17. //+----------------+
18. };
19. //+------------------------------------------------------------------+
20. #define PrintX(x) PrintFormat("0x%08X -> %s", x, #x)
21. //+------------------------------------------------------------------+
22. void OnStart(void)
23. {
24.     C_Demo a;
25. 
26.     PrintX(a.Check_1());
27.     PrintX(GetPointer(a));
28.     PrintX(a.Check_2());
29. }
30. //+------------------------------------------------------------------+

Código 03

Quando este código 03 for executado no terminal do MetaTrader 5, ele irá gerar o que é visto na imagem logo abaixo.

Imagem 04

Agora preste atenção para entender o que são estes valores presentes nesta imagem 04. Você pode notar que estamos declarando uma classe na linha quatro. E que nas linhas oito e treze, temos duas funções. Ok, a primeira função, que é vista na linha oito, irá utilizar o operador this, para obter o endereço de memória onde a classe se encontra. Para isto, usamos o que é visto sendo implementado na linha dez. Já a segunda função, que é vista na linha treze, iremos fazer algo diferente. No caso iremos usar uma construção, muito parecida com o que é visto na linha 34 do código 02. Isto para retornar o endereço de memória onde a instancia se encontra.

Observe que a única variável declarada neste código 03, se encontra na linha 24. Isto para que possamos referenciar as duas funções presentes na classe. Agora vem a parte curiosa. Observe que na linha 26, estamos imprimindo o valor que será retornado pela função da linha oito. Na linha 27 estaremos imprimindo o endereço de memória onde a variável declarada na linha 24 foi colocada. E na linha 28 estaremos imprimindo o valor resultante da função da linha treze. Ao fazer isto, você claramente pode notar que as duas primeiras informações impressa no terminal são iguais. Mostrando que o operador this, de fato estará utilizando uma referência já criada. Diferente do que acontece, no caso da terceira linha impressa no terminal. Que mostra justamente o fato de que, a linha quinze no código 03, estará de fato criando uma nova instancia.

Bem, dado este fato, creio que tenha ficado bastante claro e evidente, quando e como usar o operador this. A fim de retornar algum tipo de referência a uma classe já presente na memória.

Contudo, existe uma questão, referente a sobrecarga de operadores, da qual quero abordar. No entanto, gostaria de que você, procura-se se atentar ao nível de sutileza que será visto aqui. Mas que acaba criando resultados em certas circunstâncias que são extremamente bizarros. Como o que será mostrando, é algo um tanto quanto temeroso. Gostaria que você procurasse estudar o assunto com calma. E não tire conclusões precipitadas. Mas para separar os assuntos, vamos abrir um novo tópico.


Cuidado com a sobrecarga de operadores

Uma coisa que muita gente ignora é o fato de que programas podem gerar resultados estranhos em situações específicas. Muita gente acredita, que computadores, sempre darão resultados corretos, desde que venha a ser programados de maneira correta. Porém nem sempre é assim. O que iriei mostrar aqui, é algo que dificilmente você irá ver alguém fazer. Ou se quer mostrar. Isto por que, é um tipo de situação bem específica, na qual um código, hora gera o resultado correto, hora gera o resultado errado. E por mais estranho que possa parecer, a programação estará correta.

Bem, o código que iremos usar, é um que guardo a anos, já que foi algo que criei originalmente em C++, esperando um momento para poder finalmente passar como conhecimento para outros programadores. Mesmo por que, para entender o que será visto, seria preciso entender uma série de conceitos, dos quais, você, meu caro leitor, que vem acompanhando está série de artigos, já deve conhecer.

O código em questão pode ser visto logo abaixo.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. struct stComplex
05. {
06.     private:
07. //+----------------+
08.         double re;
09.         double im;
10. //+----------------+
11.     public  :
12. //+----------------+
13.         stComplex() :re(0), im(0) {} 
14. //+----------------+
15.         stComplex(const stComplex &o) :re(o.re), im(o.im) {}
16. //+----------------+
17.         stComplex(const double r, const double i) :re(r), im(i) {} 
18. //+----------------+
19.         stComplex operator+=(const stComplex &arg)
20.         {
21.             this = this + arg;            
22.             return this;
23.         }
24. //+----------------+
25.         stComplex operator+(const stComplex &arg)
26.         {
27.             stComplex res;
28. 
29.             res.re = this.re + arg.re;
30.             res.im = this.im + arg.im;
31. 
32.             return res;
33.         };
34.         double GetReal(void) const { return re; }
35. //+----------------+
36.         double GetImaginary(void) const { return im; }
37. //+----------------+
38. };
39. //+------------------------------------------------------------------+
40. #define PrintX(x) Print(__LINE__, " :: ", #x, " -> ", x.GetReal(), " ", x.GetImaginary(), "i")
41. //+------------------------------------------------------------------+
42. void OnStart(void)
43. {
44.     stComplex   a(2, 5),
45.                 b(8, -3),
46.                 c;
47. 
48.     c = a;
49.     PrintX(a);
50.     PrintX(b);
51.     PrintX(c);
52.     PrintX((a += b));
53.     c += b;
54.     PrintX(c);
55. }
56. //+------------------------------------------------------------------+

Código 04

Agora preste muita atenção. Este código 04, tem a princípio, o mesmo tipo de objetivo visto nos outros códigos mostrados neste artigo. Porém este código tem um problema, do qual você, que esteja iniciando na programação, dificilmente irá conseguir resolver. E mesmo enviando este código a outros programadores, eles não encontrar de fato uma falha no mesmo. Já que o código hora produz um resultado correto. Hora produz um resultado errado. Isto é claro dependendo do tipo de coisa que você esteja tentando fazer. Para demonstrar isto, veja o resultado da execução logo abaixo.

Imagem 05

A região destacada é o que nos interessa de fato. Agora pergunto: Por que os resultados estão tão diferentes? Se os valores utilizados são iguais, e a própria fatoração que está sendo feita, também é a mesma. Você pode observar a fatoração sendo feita, nas linhas 52 e 53 do código 04. Porém apesar de ser o mesmo tipo de operação, com os mesmos valores, o resultado é completamente diferente.

Você pode estar pensando: Cara, mas esta programação, não faz o mínimo sentido. Por que você está usando estes parênteses duplos na linha 52? Talvez seja isto que esteja produzindo os resultados errados. Bem, este não é o problema aqui, meu caro leitor. Você não está entendendo. Mas vou tentar explicar de uma outra maneira. Talvez fique um pouco mais simples de entender onde está o problema.

Se modificarmos este código 04, usando o fragmento logo abaixo, a coisa começa a mudar de figura.

                   .
                   .
                   .
41. //+------------------------------------------------------------------+
42. void OnStart(void)
43. {
44.     stComplex   a(2, 5),
45.                 b(8, -3),
46.                 c;
47. 
48.     c = a;
49.     PrintX(a);
50.     PrintX(b);
51.     PrintX(c);
52.     PrintX((a + b));
53.     PrintX((a += b));
54.     c += b;
55.     PrintX(c);
56. }
57. //+------------------------------------------------------------------+

Fragmento 06

Agora quando executamos o código, com estas modificações vista no fragmento 06. Eis que o resultado é o que podemos ver logo abaixo.

Imagem 06

Novamente a região destacada é a que nos interessa. Veja que o problema não são os parênteses duplos. Eles têm motivo para estarem ali. Mas sim, algum tipo de erro estanho, ou algum tipo de interação bem pouco comum está acontecendo aqui. Mas porquê? Bem, se você ainda não conseguiu entender o quanto estranho é este tipo de falha. Deixe-me te explicar. Se você observe e prestar atenção ao código 04. Irá notar que estamos usando uma diretiva na linha 40, para efetuar a depuração dos valores internos presentes na estrutura. Apesar do código funcionar, quando esta depuração não está sendo feita, como no caso da linha 54. Ela falha, quando tentamos fazer isto, como é o caso da linha 53. Isto olhando o fragmento 06. A pergunta é: Por que motivos a depuração falha em uma linha a ponto de gerar um resultado completamente estranho e sem sentido. E não falha na outra. Onde primeiro efetuamos a fatoração, para somente depois depurar os valores presentes na variável? É este o real problema aqui. E não outro que você possa estar imaginando.

Sendo assim, este tipo de coisa que está sendo vista aqui, acaba tornando muito difícil de depurar um código. Visto que se não olharmos o que está acontecendo ele irá funcionar perfeitamente bem. Porém, toda via e, entretanto, se você procurar olhar o que o código está fazendo, ele começa a gerar resultados estranhos.

Mas antes de aprofundarmos nesta questão e eu mostrar qual é a solução a ser adotada a fim de impedir este tipo de problema. Vamos entender, por que, necessitamos utilizar parênteses duplos nestas linhas 52 e 53, isto olhando o fragmento 06.

Muito bem, o motivo para se fazer isto é que sem utilizar paracenteses duplos, o compilador não irá entender o código. Mas como assim? Não entendi está sua colocação. Certo, tudo bem, vamos voltar lá no código 01, bem no começo do artigo. Veja que ali no começo do artigo, expliquei o motivo pelo qual a linha 47, vista no código 01, precisa utilizar parênteses. A mesma coisa se aplica aqui neste código 04, e também no fragmento 06. No entanto, diferente do que foi visto no código 01, aqui no código 04, na linha 52. Sem utilizarmos os parênteses duplos, o compilador iria disparar erros como os vistos logo abaixo.

Imagem 07

Isto por conta que no momento em que o compilador for resolver a definição da linha 40, a fim de criar o código visto na linha 52. Ele não iria saber como proceder. Bem, se você não faz a mínima ideia de como definições são interpretadas pelo compilador, veja este artigo Do básico ao intermediário: Definições (I). Ok, então agora está explicado o motivo dos parênteses duplos. Falta ainda entender o que está fazendo com que os valores estão sendo gerados de maneira errada. Para entender isto, precisamos adicionar uma coisa ao código 04. Desta forma ele irá ficar como mostrado logo abaixo.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. struct stComplex
05. {
06.     private:
07. //+----------------+
08.         double re;
09.         double im;
10. //+----------------+
11.     public  :
12. //+----------------+
13.         stComplex() :re(0), im(0) {} 
14. //+----------------+
15.         stComplex(const stComplex &o) :re(o.re), im(o.im) {}
16. //+----------------+
17.         stComplex(const double r, const double i) :re(r), im(i) {} 
18. //+----------------+
19.         stComplex operator+=(const stComplex &arg)
20.         {
21.             Print(__FUNCTION__);
22.             this = this + arg;            
23.             return this;
24.         }
25. //+----------------+
26.         stComplex operator+(const stComplex &arg)
27.         {
28.             stComplex res;
29. 
30.             res.re = this.re + arg.re;
31.             res.im = this.im + arg.im;
32. 
33.             return res;
34.         };
35.         double GetReal(void) const { return re; }
36. //+----------------+
37.         double GetImaginary(void) const { return im; }
38. //+----------------+
39. };
40. //+------------------------------------------------------------------+
41. #define PrintX(x) Print(__LINE__, " :: ", #x, " -> ", x.GetReal(), " ", x.GetImaginary(), "i")
42. //+------------------------------------------------------------------+
43. void OnStart(void)
44. {
45.     stComplex   a(2, 5),
46.                 b(8, -3),
47.                 c;
48. 
49.     c = a;
50.     PrintX(a);
51.     PrintX(b);
52.     PrintX(c);
53.     PrintX((a + b));
54.     PrintX((a += b));
55.     c += b;
56.     PrintX(c);
57. }
58. //+------------------------------------------------------------------+

Código 05

Aqui no código 05, adicionamos na linha 21 uma nova informação a ser impressa no terminal. Ao fazermos isto, iremos saber quantas vezes esta função implementada na linha 19 estará sendo chamada. Agora preste atenção ao seguinte fato. Se tudo estiver de acordo com o que é esperado. Esta linha 21 irá imprimir, uma mensagem ANTES da linha 54 ser executada, e uma outra ANTES da linha 56 ser executada. Entendido? Bem, então vamos compilar este código 05, e ver o que será mostrado no terminal. E para a surpresa de todos, eis que no terminal vemos o que é mostrado na imagem logo abaixo.

Imagem 08

Mas que coisa mais estranha. Em um dado momento temos duas chamadas sendo feitas, enquanto em outro momento temos apenas uma, como era esperado. Cara que coisa mais bizarra é esta? Definitivamente não estou entendendo onde está o erro aqui. Pois isto não faz o menor sentido. Como pode o compilador está gerando este tipo de coisa? De fato, meu caro leitor, este tipo de falha é muito estranho. E definitivamente algo extremamente complicado de se corrigir. Ainda mais quando estamos aprendendo, ou temos pouca experiência em programação. Ai definitivamente não saberemos como proceder. Já que à primeira vista não existe nenhuma falha aqui no código. Visto que hora ele funciona, hora não. E tudo isto depende, do fato de que estamos ou não olhando para o que esteja ocorrendo em uma determinada linha de código.

Mas apesar de toda esta maluquice mostrada aqui, a solução, e por mais estranho que possa parecer, não está onde você possa estar imaginando. Que seria no caso, alguma falha no código que esteja implementando o operador visto na linha 19. Na verdade a falha está na definição usada para depurar o código. Hã? Calma lá. Como assim? Isto que você está dizendo, faz ainda menos sentido. Como a falha pode estar na definição, se ao depurar o código, usando para isto a mudança feita no código 05. Claramente notamos que existe algo estranho acontecendo, que está fazendo com que a linha 54, venha a chamar a linha 19 duas vezes. Pois é isto que estou vendo ao olhar a imagem 08. Não consigo entender, por que a falha está na definição que é usada para depurar o código.

Mas sim, meu caro leitor, a falha está na definição. Porém, não exatamente nela. E sim no como ela está buscando os dados a serem impresso. Por algum estranho motivo. As vezes o compilador não faz as coisas como elas deveriam ser feitas. E isto é algo no qual não irei entrar em detalhes, para não tornar ainda mais confuso, algo que por si só já é confuso. No entanto, se mudarmos a forma como a diretiva PrintX está sendo criada, iremos conseguir resolver este problema visto neste tópico. E para fazer isto, precisaremos mudar o código, para algo visto logo abaixo.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. struct stComplex
05. {
06.     private:
07. //+----------------+
08.         double re;
09.         double im;
10. //+----------------+
11.     public  :
12. //+----------------+
13.         stComplex() :re(0), im(0) {} 
14. //+----------------+
15.         stComplex(const stComplex &o) :re(o.re), im(o.im) {}
16. //+----------------+
17.         stComplex(const double r, const double i) :re(r), im(i) {} 
18. //+----------------+
19.         stComplex operator+=(const stComplex &arg)
20.         {
21.             this = this + arg;            
22.             return this;
23.         }
24. //+----------------+
25.         stComplex operator+(const stComplex &arg)
26.         {
27.             stComplex res;
28. 
29.             res.re = this.re + arg.re;
30.             res.im = this.im + arg.im;
31. 
32.             return res;
33.         };
34. //+----------------+
35.         void GetInfos(double &r, double &i) { r = re; i = im; }
36. //+----------------+
37. };
38. //+------------------------------------------------------------------+
39. #define PrintX(x) { double r, i; x.GetInfos(r, i); Print(__LINE__, " :: ", #x, " -> ", r, " ", i, "i"); }
40. //+------------------------------------------------------------------+
41. void OnStart(void)
42. {
43.     stComplex   a(2, 5),
44.                 b(8, -3),
45.                 c;
46. 
47.     c = a;
48.     PrintX(a);
49.     PrintX(b);
50.     PrintX(c);
51.     PrintX((a + b));
52.     PrintX((a += b));
53.     c += b;
54.     PrintX(c);
55. }
56. //+------------------------------------------------------------------+

Código 06

Agora quando este código 06 for executado, você irá ver no terminal o que é mostrado na imagem logo abaixo.

Imagem 09

Demonstrando assim que de fato a falha foi corrigida.


Considerações finais

Este artigo, foi bastante divertido de ser escrito. Apesar do que foi visto aqui, parecer ser algo muito confuso e complicado de entender. No entanto, gostaria que você, meu caro leitor, procurasse estudar e entender muito bem o que foi explicado aqui. Pois este tipo de coisa, um dia poderá acontecer com você. E sem a devida experiência, você com certeza irá acabar ficando bem frustrado, tentando entender por que seu código está gerando hora um resultado, hora outro.

No entanto, acredito que tenho conseguido passar parte do meu conhecimento a respeito do assunto. Já que muito do que foi mostrado aqui, foi resultado de muitos anos, vendo coisas estranhas acontecendo. E sempre tendo que conseguir resolver as mesmas. Porém, se apenas e somente um de vocês, conseguir absorver o que demonstrei aqui, já terá valido a pena ter escrito este artigo. 

Arquivo MQ5Descrição
Code 01 Demonstração básica
Code 02 Demonstração básica
Code 03 Demonstração básica
Code 04 Demonstração básica
Code 05  Demonstração básica
Arquivos anexados |
Anexo.zip (2.8 KB)
Simulação de mercado: A união faz a força (I) Simulação de mercado: A união faz a força (I)
Estamos chegando aos finalmente. O desenvolvimento do replay / simulador está quase concluído. É bem verdade que ainda precisaremos fazer algumas poucas coisas. Mas frente a tudo que realmente já foi feito. Implementar o que falta será moleza. Mas como tudo que será mostrado neste artigo, precisará ser adequadamente digerido e compreendido. Quero que você, meu caro leitor e entusiasta.
Gerenciamento de riscos (Parte 2): Implementação do cálculo de lotes na interface gráfica Gerenciamento de riscos (Parte 2): Implementação do cálculo de lotes na interface gráfica
Neste artigo, analisaremos como aprimorar e aplicar de forma mais eficiente os conceitos apresentados no artigo anterior, utilizando as poderosas bibliotecas de elementos gráficos de controle do MQL5. Conduzirei você passo a passo pelo processo de criação de uma interface gráfica totalmente funcional, explicando o plano de projeto subjacente, bem como o propósito e o princípio de funcionamento de cada método empregado. Além disso, ao final do artigo testaremos o painel criado, a fim de confirmar seu correto funcionamento e sua aderência aos objetivos estabelecidos.
Gerenciamento de riscos (Parte 3): Criação da classe principal de gerenciamento de riscos Gerenciamento de riscos (Parte 3): Criação da classe principal de gerenciamento de riscos
Neste artigo começaremos a criação da classe principal de gerenciamento de riscos, que será o elemento chave para o controle de riscos no sistema. Vamos nos concentrar na construção das bases, na definição das principais estruturas, variáveis e funções. Além disso, implementaremos os métodos necessários para atribuir valores de lucro máximo e prejuízo máximo, estabelecendo assim o alicerce do gerenciamento de riscos.
Desenvolvimento de um Kit de Ferramentas para Análise da Ação do Preço (Parte 6): Mean Reversion Signal Reaper Desenvolvimento de um Kit de Ferramentas para Análise da Ação do Preço (Parte 6): Mean Reversion Signal Reaper
Embora alguns conceitos possam parecer simples à primeira vista, trazê-los à prática pode ser bastante desafiador. No artigo abaixo, levaremos você a uma jornada pela nossa abordagem inovadora para automatizar um Expert Advisor (EA) que analisa o mercado de forma eficiente utilizando uma estratégia de reversão à média. Junte-se a nós enquanto desvendamos as complexidades desse empolgante processo de automação.