Return Struct When Struct has Constructor - page 2

 

Thank you all for the support.

Seems that the issue was the lack of default constructor for the trade_record strucut. Bellow is the compiling code.


//+------------------------------------------------------------------+
//|                                                   TestStruct.mq5 |
//|                                  Copyright 2022, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2022, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property indicator_chart_window
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping

//---
   return(INIT_SUCCEEDED);
  }


struct trade_record
  {
   double            sl;
   double            tp;
   double            longVolume;
   double            shortVolume;
   bool              issue;
   string            message;




                     trade_record(double _sl, double _tp)
     {
      this.sl = _sl;
      this.tp = _tp;
     };


                     trade_record() :
                     sl(NULL),
                     tp(NULL),
                     longVolume(NULL),
                     shortVolume(NULL),
                     issue(NULL),
                     message(NULL)
     {};

  };


//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
trade_record getTradeRecord()
  {
   trade_record result(10, 10);

   return result;
  }


//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &price[])
  {
//---

   trade_record record = getTradeRecord();
   

//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+


Onde more questions if possible. Is it possible to initialize a struct without using a default constructor, the ideia here is to guarantee some invariants possible using construct overloads. Or for this scenario would be better to use classes instead?


Below is the example an exemple of the ideia:

//+------------------------------------------------------------------+
//|                                                   TestStruct.mq5 |
//|                                  Copyright 2022, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2022, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property indicator_chart_window
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping

//---
   return(INIT_SUCCEEDED);
  }


struct trade_record
  {
   double            sl;
   double            tp;
   double            longVolume;
   double            shortVolume;
   bool              issue; // represents an issue, with more details in the message field.
   string            message;


   // Success Case
                     trade_record(double _sl, double _tp, double _longVolume, double _shortVolume):
                     sl(_sl),
                     tp(_tp),
                     longVolume(_longVolume),
                     shortVolume(_shortVolume) {};


   // Error Case
                     trade_record(string _message):
                     issue(true),
                     message(_message) {};


                     trade_record() :
                     sl(NULL),
                     tp(NULL),
                     longVolume(NULL),
                     shortVolume(NULL),
                     issue(NULL),
                     message(NULL)
     {};

  };


//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
trade_record getTradeRecord()
  {
   trade_record sucessResult(10, 10, 10, 10);

   trade_record errorResult("Something when wrong ... :(");

   bool isEverythingFine = true; // make some calculation to check if everything is fine ...

   if(isEverythingFine)
      return sucessResult;
   else
      return errorResult;

  }


//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &price[])
  {
//---

   trade_record record = getTradeRecord();


//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+


To summarize, the goal here is to find a idiomatic way in MQL5, to avoid creating objects that are in a inconsistent state, like in the default constructor of trade_record.

 
Cássio da Rosa #:

To summarize, the goal here is to find a idiomatic way in MQL5, to avoid creating objects that are in a inconsistent state, like in the default constructor of trade_record.

If you are worried about inconsistent initialization, you should code it so it can never happen. Either make sure the default constructor has the right defaults, or use an overloaded constructor and pass the params you want. Or simplest of all, create a method to set the values you want which can be called anytime, even later in the program (this is my preference as per my first example).

 
R4tna C #:

If you are worried about inconsistent initialization, you should code it so it can never happen. Either make sure the default constructor has the right defaults, or use an overloaded constructor and pass the params you want. Or simplest of all, create a method to set the values you want which can be called anytime, even later in the program (this is my preference as per my first example).


I get your ideia. Since the default constructor puts trade_record in a inconsistent state, I would like to not use the default constructor approach. Are there any other way to not use default constructor?


The goal is to make impossible to create an inconsistent trade_record. Are there any other idiomatic MQL5 solution for this scenario?


I was able to explain my point about the make impossible to create inconsistent state? And find idiomatic MQL5 solutions to avoid it?


Cássio da Rosa #:

Is it possible to initialize a struct without using a default constructor, the ideia here is to guarantee some invariants possible using construct overloads. Or for this scenario would be better to use classes instead?

 
Cássio da Rosa #:


I get your ideia. Since the default constructor puts trade_record in a inconsistent state, I would like to not use the default constructor approach. Are there any other way to not use default constructor?


The goal is to make impossible to create an inconsistent trade_record. Are there any other idiomatic MQL5 solution for this scenario?


I was able to explain my point about the make impossible to create inconsistent state? And find idiomatic MQL5 solutions to avoid it?


With structures you don't need to have an any constructors or methods unless you want to - the choice is yours.

I hope this code explains it (I did not create any example with constructors as that is shown in the earlier examples)

   //1. Struct with no constructors or methods
   struct a_structure_definition
     {
      string         value;
     };
   
   a_structure_definition a_structure;
   a_structure.value = "a string";


   //2. Struct with no constructors but 1 setter method
   struct b_structure_definition
     {
      string         value;
      void           set_value(string arg1)
        {
         value = arg1;
        }
     };

   b_structure_definition b_structure;
   b_structure.set_value("arg1");

 
As addendum, a default constructor is a constructor without parameters.

It is very well possible to initialize members also using functions. So instead of passing a value, which can be chosen to any valid value, you could also call a function and let the return value be assigned to the member.

Hope this is understandable.

I still suggest to always have a copy constructor, especially when you implicit create the object, like in a return statement.

Anyways, there is no difference between a structure and a class, concerning constructors or destructors. This will only make a difference in MQL as you begin to use pointers to objects and try to use dynamic dereferencing of objects. (Base object pointer Type pointing to derived classes).

Same for destructors.
 

Awesome, thank all for the iterations.

Starting to realize, that the goal is something more OOP, possible using encapsulation of the members, below is a working example.

//+------------------------------------------------------------------+
//|                                                    TestClass.mq5 |
//|                                  Copyright 2022, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2022, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property indicator_chart_window
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping

//---
   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class TradeRecord
  {
private:
   double            sl;
   double            tp;
   double            longVolume;
   double            shortVolume;

   bool              issue; // represents an issue, with more details in the message field.y
   string            message;
public:
   // Success Case
                     TradeRecord(double _sl, double _tp, double _longVolume, double _shortVolume):
                     sl(_sl),
                     tp(_tp),
                     longVolume(_longVolume),
                     shortVolume(_shortVolume) {};
   // Error Case
                     TradeRecord(string _message):
                     issue(true),
                     message(_message) {};

   string            GetState()
     {
      if(!issue)
         return StringFormat("TP: %g, SL %g, LongVolume: %g, ShortVolume: %g", tp, sl, longVolume, shortVolume);
      else
         return StringFormat("%s", message);
     }

                     TradeRecord(const TradeRecord& _record)
     {
      this = _record;
     };
  };


//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
TradeRecord getTradeRecord()
  {

   bool isEverythingFine = true; // make some calculation to check if everything is fine ...

   if(isEverythingFine)
     {
      TradeRecord successCase(10, 10, 10, 10);
      return successCase;
     }
   else
     {
      TradeRecord errorCase("Something when wrong ... :(");
      return errorCase;
     }
  }


//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &price[])
  {
//---

   TradeRecord record = getTradeRecord();
   
   Comment(record.GetState());
   
   // Print(record.GetState());   

//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+


Using this approach is impossible to get an inconsistent state of TradeRecord.


So, trying to improve it, using interfaces, starting getting some errors, code below:

//+------------------------------------------------------------------+
//|                                                    TestClass.mq5 |
//|                                  Copyright 2022, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2022, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property indicator_chart_window
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping

//---
   return(INIT_SUCCEEDED);
  }


interface ITradeCase
  {
   string GetState();
  };



//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class TradeCaseSuccess : public ITradeCase
  {
   double            sl;
   double            tp;
   double            longVolume;
   double            shortVolume;

public:

                     TradeCaseSuccess(const TradeCaseSuccess& _record)
     {
      this = _record;
     };


                     TradeCaseSuccess(double _sl, double _tp, double _longVolume, double _shortVolume):
                     sl(_sl),
                     tp(_tp),
                     longVolume(_longVolume),
                     shortVolume(_shortVolume) {};


   string            GetState()
     {

      return StringFormat("TP: %g, SL %g, LongVolume: %g, ShortVolume: %g", tp, sl, longVolume, shortVolume);
     }
  };

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class TradeCaseError : public ITradeCase
  {
   string            message;

public:
                     TradeCaseError(string _message): message(_message) {};

                     TradeCaseError(const TradeCaseError& _record)
     {
      this = _record;
     };


   string            GetState()
     {
      return message;
     }
  };



//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
ITradeCase getTradeRecord()
  {

   bool isEverythingFine = true; // make some calculation to check if everything is fine ...

   if(isEverythingFine)
     {
      TradeCaseSuccess successCase(10, 10, 10, 10);
      return successCase;
     }
   else
     {
      TradeCaseError errorCase("Something when wrong ... :(");
      return errorCase;
     }
  }


//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &price[])
  {
//---

 ITradeCase record = getTradeRecord();

 Comment(record.GetState());

//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+


Follow as attachment the compilation error.

Discover new MetaTrader 5 opportunities with MQL5 community and services
Discover new MetaTrader 5 opportunities with MQL5 community and services
  • 2022.07.13
  • www.mql5.com
MQL5: language of trade strategies built-in the MetaTrader 5 Trading Platform, allows writing your own trading robots, technical indicators, scripts and libraries of functions
Files:
 

It worked returning a Pointer of the interface ITradeCase

#property copyright "Copyright 2022, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property indicator_chart_window

int OnInit()
  {
   return(INIT_SUCCEEDED);
  }


interface ITradeCase
  {
   virtual string GetState();
  };



//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class TradeCaseSuccess : public ITradeCase
  {
   double            sl;
   double            tp;
   double            longVolume;
   double            shortVolume;

public:

                     TradeCaseSuccess(const TradeCaseSuccess& _record)
     {
      this = _record;
     };


                     TradeCaseSuccess(double _sl, double _tp, double _longVolume, double _shortVolume):
                     sl(_sl),
                     tp(_tp),
                     longVolume(_longVolume),
                     shortVolume(_shortVolume) {};


   string            GetState()
     {
      return StringFormat("TP: %g, SL %g, LongVolume: %g, ShortVolume: %g", tp, sl, longVolume, shortVolume);
     }
  };

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class TradeCaseError : public ITradeCase
  {
   string            message;

public:
                     TradeCaseError(string _message): message(_message) {};

                     TradeCaseError(const TradeCaseError& _record)
     {
      this = _record;
     };


   string            GetState()
     {
      return message;
     }
  };



//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
ITradeCase* getTradeRecord()
  {

   bool isEverythingFine = true; // make some calculation to check if everything is fine ...

   if(isEverythingFine)
     {
      return new TradeCaseSuccess(10, 10, 10, 10);
     }
   else
     {
      return new TradeCaseError("Something when wrong ... :(");
     }
  }


//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &price[])
  {
//---

   ITradeCase* record = getTradeRecord();


   Comment(record.GetState());

   delete record;

//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+

Any tips in make it more idiomatic are welcome.

 
Cássio da Rosa #:

Starting to realize, that the goal is something more OOP, possible using encapsulation of the members, below is a working example.

As you want it to be more OOP, then use objects from the start - we have explored this topic quite extensively, so to conclude I suggest you embed the struct in an object.

The passive structure is very handy if you want to re-use a common set of fields in other places.

Small example attached to demonstrate the array, objects, structure and function all speak the same language - add/removing fields across all these is easy.

//+------------------------------------------------------------------+
struct commonDataFields
//+------------------------------------------------------------------+
  {
   string            str;
   int               integer;
  };

//+------------------------------------------------------------------+
commonDataFields arrayOfFields[9];
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
struct containerStruc
//+------------------------------------------------------------------+
  {
   commonDataFields  dataSet1;
   commonDataFields  dataSet2;
  };


//+------------------------------------------------------------------+
class object1
//+------------------------------------------------------------------+
  {
public:
   commonDataFields  data1;

                     object1(void);
                    ~object1(void);
   void              setData(commonDataFields &receive)
     {
      data1.str     = receive.str;
      data1.integer = receive.integer;
     }
  };

//+------------------------------------------------------------------+
class object2
//+------------------------------------------------------------------+
  {
public:
   commonDataFields  data2;

                     object2(void);
                    ~object2(void);

   void              setData(commonDataFields &receive)
     {
      data2.str     = StringSubstr(receive.str, 0, 1);
      data2.integer = receive.integer / 2;
     }
  };


//+------------------------------------------------------------------+
string getStringFromDataStruc(commonDataFields &receive)
//+------------------------------------------------------------------+
  {
   return (receive.str);
  }
 
You could template your classes so that you can handle any type of structure as dataset within the template class.

For a more pragmatic way if going forward, I suggest using "procedural approach" to handle any processing logic, and use OOP only for data handling.

You will see, OOP can get out of hand very fast, and turn into nightmare, once your project gets a little bigger.

I have for my usage OOP to represent data containers like ring buffers, fifo and filo buffers, ordered and unordered containers, uid objects, object chains, a smart handle object, and smart pointers object.

There is no processing logic represented in OOP throughout my code, it makes life so much easier this way.

Although I think "hybrid objects" like a new period object can get in very handy to represent certain states throughout the code. Either as singleton object or as global object, depending on how you design the overall layout of your code.

Anyways, very good progress you did there.
 
Dominik Christian Egert #:
You could template your classes so that you can handle any type of structure as dataset within the template class.

For a more pragmatic way if going forward, I suggest using "procedural approach" to handle any processing logic, and use OOP only for data handling.

You will see, OOP can get out of hand very fast, and turn into nightmare, once your project gets a little bigger.

I have for my usage OOP to represent data containers like ring buffers, fifo and filo buffers, ordered and unordered containers, uid objects, object chains, a smart handle object, and smart pointers object.

There is no processing logic represented in OOP throughout my code, it makes life so much easier this way.

Although I think "hybrid objects" like a new period object can get in very handy to represent certain states throughout the code. Either as singleton object or as global object, depending on how you design the overall layout of your code.

Anyways, very good progress you did there.
Could post a code example please of this approach?
I am always curious and happy to learn new things 
Reason: