MQL's OOP notes: Managing resources by object wrappers

16 September 2016, 21:06
Stanislav Korotky
0
174
MQL programmers use files here and there. Standard API provides a set of file functions which employ old plain procedural approach. But sometimes it's better to upgrade this style up to OOP.

One simple use case where OOP can be helpful is related to resource control. A file is a resource of a special kind. It should be allocated (created), used, and finally released (closed).

Usual practice is to call: FileOpen, then some of FileRead/FileWrite functions, and finally FileClose. All looks fine as long as algorithm stays straightforward. Once it gets branches and multiple return operators, the task of releasing resources becomes a bit complicated. You should remeber about all exit points and copy FileClose to many places in the code.

This problem can be easily solved by OOP. Let us define a class which wraps file handle and takes care about (potentially, all) manipulations with it. To open a file one will create an automatic object of this class, for example inside a function. And when the flow control will leave the context (the function in this case, or other block of code where the file variable is declared), the object destructor will be called automatically and close file handle. It's important to note that the variable should be declared as automatic, not via a dynamically allocated pointer using operator new (because such a stuff must be deleted explicitly by programmer).  

Here is the simple self-explanatory class.

class File
{
  private:
    int file; // file handle
  
  public:
    File(const string name, const int flags, const short delimiter)
    {
      file = FileOpen(name, flags, delimiter);
    }

    File(const string name, const int flags)
    {
      file = FileOpen(name, flags);
    }
    
    bool isOpened() const
    {
      return (file != INVALID_HANDLE);
    }
    
    bool operator!() const
    {
      return !isOpened();
    }
    
    int handle() const
    {
      return file;
    }
    
    virtual ~File()
    {
      if(file != INVALID_HANDLE)
      {
        Print("File is closed automatically"); // this is just a debug log
        FileClose(file);
      }
    }
};
Having this class one can use it as follows.

int processFile(const string inputFileName = "abc.csv")
{
  // declare automatic variable of class File
  File f(inputFileName, FILE_READ | FILE_TXT | FILE_SHARE_READ, ',');
  if(!f)
  {
    Alert("Can't read file " + inputFileName);
    return 1;
  }
  
  int handle = f.handle();

  // these are just placeholders for some actual conditions  
  bool requiredCondition1, requiredCondition2;
  
  Print("Processing started...");
  
  if(!requiredCondition1)
  {
    // FileClose(handle) call is not required
    return 2;
  }
  
  string line = FileReadString(handle);

  Print("More in-depth processing...");

  if(!requiredCondition2)
  {
    // FileClose(handle) call is not required
    return 3;
  }
  
  // ...
  
  Print("Processing done");
  
  // FileClose(handle) call is not required

  return 0;
}
Closing the file is important because otherwise it can be left locked and inaccessible for other parts of algorithm or other programs. This class makes the job for you automatically.



Files:
file.mqh  2 kb