MQL4: Are Objects Always Passed by Reference?

Understanding how MQL4 handles data, particularly complex types often referred to as “objects,” is crucial for developing robust and efficient trading algorithms. A common question among developers is whether these objects are always passed by reference. This article delves into this topic, clarifying MQL4’s behavior and its implications.

Introduction to Objects and References in MQL4

Brief Overview of Objects in MQL4

In the context of MQL4, the term “objects” can be slightly ambiguous compared to languages with full-fledged object-oriented programming (OOP) capabilities like MQL5. MQL4 does not support classes in the way MQL5 does. When discussing “objects” in MQL4 that can be passed to functions, we are primarily referring to:

  • Structures (struct): User-defined composite data types that group variables under a single name.
  • Arrays: Collections of data of the same type.
  • Strings: Sequences of characters.

Graphical objects (like lines, arrows drawn on charts) are manipulated through specific functions and handles, which is a different aspect of “objects” in MetaTrader.

Understanding Pass by Value vs. Pass by Reference

Before diving into MQL4 specifics, let’s briefly recap these fundamental concepts:

  • Pass by Value: When a variable is passed by value to a function, a copy of the variable’s actual value is made and passed to the function. Modifications to the parameter inside the function do not affect the original variable outside the function.
  • Pass by Reference: When a variable is passed by reference, an alias or a pointer to the original variable’s memory location is passed to the function. Any modifications to the parameter inside the function directly affect the original variable.

MQL4 Object Handling: The Reference Paradigm

The core answer to the question is: Yes, in MQL4, complex data types such as structures, arrays, and strings are passed to functions by reference. This is a fundamental behavior of the language designed for efficiency, especially with potentially large data structures.

Exploring Object Variable Behavior

When you pass an MQL4 struct, array, or string to a function, you are not passing a copy of the entire data structure. Instead, the function receives a reference to the original data. This means that operations performed on the parameter within the function will alter the original data instance.

Consider the assignment of one struct variable to another. In MQL4, this also typically creates a reference if not handled carefully for deep copying:

struct MyData {
  int id;
  string description;
};

// ... in some function ...
MyData data1;
data1.id = 1;
data1.description = "Initial Data";

MyData data2 = data1; // data2 now refers to the same data as data1 for strings
                     // For structs, assignment IS a member-wise copy, not a reference assignment
                     // However, string members *within* the struct are reference types

// Clarification: Direct struct assignment data2 = data1; in MQL4 performs a member-wise copy for the struct itself.
// However, string members *within* the struct are reference types. If data1.description is modified *after* assignment to data2,
// data2.description will reflect that change if the string itself was assigned by reference initially.
// The critical point is how *functions* handle structs passed as arguments.

Let’s focus on function arguments, which is the primary context of “pass by reference.”

Code Examples Demonstrating Object Reference

Let’s illustrate with an MQL4 struct:

// Define a simple structure
struct PricePoint {
  double open;
  double high;
  double low;
  double close;
  string symbol;
};

// Function that modifies a PricePoint structure
void ModifyPricePoint(PricePoint &pt) { // In MQL4, the '&' is implicit for structs
  pt.high = pt.high + 10.0; // Modify a field
  pt.symbol = pt.symbol + "_Modified"; // Modify string field
  Print("Inside ModifyPricePoint: High = ", pt.high, ", Symbol = ", pt.symbol);
}

// OnInit or OnStart function
int OnInit() {
  PricePoint currentCandle;
  currentCandle.open = 1.12000;
  currentCandle.high = 1.12500;
  currentCandle.low = 1.11900;
  currentCandle.close = 1.12400;
  currentCandle.symbol = "EURUSD";

  Print("Before ModifyPricePoint: High = ", currentCandle.high, ", Symbol = ", currentCandle.symbol);

  // Pass the structure to the function
  // MQL4 passes structs by reference automatically
  ModifyPricePoint(currentCandle); 

  Print("After ModifyPricePoint: High = ", currentCandle.high, ", Symbol = ", currentCandle.symbol);
  // Output will show that currentCandle was modified

  return(INIT_SUCCEEDED);
}

In the example above, the ModifyPricePoint function receives currentCandle. Any changes to pt inside ModifyPricePoint directly alter currentCandle in OnInit. This is characteristic of pass-by-reference behavior. Note that MQL4 does not require an explicit & for passing structs by reference to functions; it’s the default behavior. In MQL5, you would typically use & to explicitly denote passing by reference for structs, although MQL5 class objects are always passed by reference (handle).

Arrays exhibit similar behavior:

void ModifyArray(double &arr[]) { // '&' is optional for arrays but good for clarity
  if (ArraySize(arr) > 0) {
    arr[0] = 999.99;
  }
}

// ... in OnInit or OnStart ...
double prices[] = {1.0, 2.0, 3.0};
Print("Before ModifyArray: prices[0] = ", prices[0]);
ModifyArray(prices);
Print("After ModifyArray: prices[0] = ", prices[0]); // prices[0] will be 999.99

Implications of Passing Objects by Reference

Understanding this reference-based approach is vital for several reasons:

Object Modification Effects

  • Efficiency: Passing by reference avoids the overhead of copying potentially large data structures (like extensive arrays or complex structs). This is beneficial for performance, especially in frequently called functions or within loops.
  • Side Effects: The primary implication is that functions can modify the original data. This can be intentional and useful, but it can also lead to bugs if modifications are unintended or if the programmer forgets that the original data is being changed.
  • Shared State: When multiple parts of a program hold references to the same data structure, changes made through one reference are visible through all others. This requires careful state management.

Memory Management Considerations

  • No Duplication: Since no copies are made when passing by reference, it’s memory-efficient. You’re working with the same block of memory.
  • Lifetime: The lifetime of the data is tied to its original declaration. MQL4 manages memory for structs and arrays declared on the stack or globally. For dynamically sized arrays, MQL4 handles their resizing and memory internally. There’s no manual memory deallocation for structs or standard arrays in MQL4 like delete in MQL5 for class objects created with new.

In MQL5, while class objects are also passed by reference (their handle/pointer is passed), MQL5 offers more explicit control with new and delete for objects, and the const keyword can be used to prevent modification of parameters passed by reference, enhancing safety.

Alternatives and Workarounds for Object Copying

Since MQL4 passes structs by reference by default to functions, if you need to pass a copy to ensure the original data remains unchanged, you must do so manually.

Manual Object Value Replication

To achieve a true pass-by-value semantic for a struct, you need to create a new struct instance and copy the members from the original to the new one before passing it to the function, or perform the copy inside the function if the goal is to work on a local duplicate.

struct Point {
  int x;
  int y;
};

// Function to create a copy of a Point struct
Point CopyPoint(Point &original) { // Still receives by reference
  Point newPoint;
  newPoint.x = original.x;
  newPoint.y = original.y;
  return newPoint; // Returns a copy
}

void ProcessPointCopy(Point pt_copy) { // pt_copy is now a distinct copy IF it was returned from CopyPoint
  pt_copy.x = pt_copy.x * 10;
  Print("Inside ProcessPointCopy: x = ", pt_copy.x);
}

// ... in OnInit or OnStart ...
Point p1;
p1.x = 5;
p1.y = 10;

Print("Original p1.x = ", p1.x);

// Option 1: Create copy before calling
Point p1_copy = CopyPoint(p1);
ProcessPointCopy(p1_copy); // Pass the copy

// Option 2: Function that expects a value (MQL4 still passes struct by ref, so it needs to make its own copy)
// To truly simulate pass-by-value for a function argument, the function itself would need to copy its parameter:
void ProcessPointMakeInternalCopy(Point &pt_ref) { // Receives by reference
  Point local_copy; // Create a local copy
  local_copy.x = pt_ref.x;
  local_copy.y = pt_ref.y;

  local_copy.x = local_copy.x * 100; // Modify the local copy
  Print("Inside ProcessPointMakeInternalCopy: local_copy.x = ", local_copy.x);
}

ProcessPointMakeInternalCopy(p1);
Print("After all processing, original p1.x = ", p1.x); // p1.x remains 5

For arrays, you can use ArrayCopy to create a duplicate array.

Limitations and Performance

  • Overhead: Manual copying introduces performance overhead, both in terms of CPU cycles for the copying process and memory for the new instance. This can be significant for large or frequently copied structures/arrays.
  • Complexity: Implementing deep copies (if structs contain other structs or arrays that also need copying) can become complex and error-prone.

Conclusion: Solidifying Understanding of MQL4 Object References

Recap of Key Concepts

  • In MQL4, complex data types like structs, arrays, and strings are passed to functions by reference by default.
  • This means that functions operate directly on the original data, not a copy.
  • This behavior is chosen for efficiency but requires developers to be aware of potential side effects (unintended modifications).
  • If true value semantics (passing a copy) are required for structs, manual duplication of the data is necessary.

Best Practices for Working with Objects in MQL4

  1. Be Aware: Always remember that modifications to struct, array, or string parameters within a function will affect the original data.
  2. Document Intent: Clearly document whether a function is intended to modify its parameters. Use descriptive function names, e.g., CalculateMetricsReadOnly(PricePoint data) versus NormalizePricePoint(PricePoint &data).
  3. Explicit Copying When Needed: If a function needs to modify data locally without affecting the caller’s original data, create an explicit copy of the struct or array at the beginning of the function or before calling it.
  4. Simplicity for MQL4: Given MQL4’s nature, keep data structures and their handling straightforward. Complex nested structures that require deep copying can become cumbersome.
  5. Consider MQL5 for Advanced OOP: If you require more sophisticated object handling, including constructors, destructors, inheritance, and explicit control over pass-by-reference (&) with const correctness, MQL5 is the more suitable language.

By understanding MQL4’s reference-passing mechanism for its “object-like” types, developers can write more predictable, efficient, and bug-free trading algorithms.


Leave a Reply