Tipos anidados, espacios de nombres y operador de contexto '::'

Las clases, estructuras y uniones pueden describirse no sólo en el contexto global, sino también dentro de otra clase o estructura. Es más: la definición puede hacerse dentro de la función. Esto permite describir todas las entidades necesarias para el funcionamiento de cualquier clase o estructura dentro del contexto adecuado y evitar así posibles conflictos de nombres.

En concreto, en el programa de dibujo, la estructura para almacenar las coordenadas Pair se ha definido hasta ahora de forma global. A medida que el programa crezca, es muy posible que se necesite otra entidad llamada Pair (sobre todo, teniendo en cuenta el nombre, más bien genérico). Por lo tanto, es conveniente trasladar la descripción de la estructura al interior de la clase Shape (Shapes6.mq5).

class Shape
{
public:
   struct Pair
   {
      int xy;
      Pair(int aint b): x(a), y(b) { }
   };
   ...
};

Las descripciones anidadas tienen permisos de acceso de acuerdo con los modificadores de sección especificados. En este caso, hemos puesto el nombre Pair a disposición del público. Dentro de la clase Shape, el manejo del tipo de estructura Pair no cambia en modo alguno debido a la transferencia. No obstante, en el código externo debe especificar un nombre completo que incluya el nombre de la clase externa (contexto), el operador de selección de contexto '::' y el propio identificador de entidad interna. Por ejemplo, para describir una variable con un par de coordenadas, tendría que escribir:

Shape::Pair coordinates(00);

El nivel de anidamiento al describir entidades no está limitado, por lo que un nombre totalmente cualificado puede contener identificadores de varios niveles (contextos) separados por '::'. Por ejemplo, podríamos envolver todas las clases de dibujo dentro de la clase externa Drawing, en la sección public.

class Drawing
{
public:
   class Shape
   {
   public:
      struct Pair
      {
         ...
      };
   };
   class Rectangle : public Shape
   {
      ...
   };
   ...
};

Entonces, los nombres de tipos totalmente cualificados (por ejemplo, para su uso en OnStart u otras funciones externas) se alargarían:

Drawing::Shape::Rect coordinates(00);
Drawing::Rectangle rect(2001007050clrBlue);

Por un lado, esto es una inconveniencia, pero por otro, a veces es una necesidad en proyectos grandes con un amplio número de clases. En nuestro pequeño proyecto, este enfoque se utiliza únicamente para demostrar la viabilidad técnica.

Para combinar clases y estructuras relacionadas lógicamente en grupos con nombre, MQL5 proporciona una forma más sencilla que incluirlas en una clase envoltorio «vacía».

Un espacio de nombres se declara utilizando la palabra clave namespace seguida del nombre y un bloque de llaves que incluye todas las definiciones necesarias. Este es el aspecto del mismo programa de pintura utilizando namespace:

namespace Drawing
{
   class Shape
   {
   public:
      struct Pair
      {
         ...
      };
   };
   class Rectangle : public Shape
   {
      ...
   };
   ...
}

Existen dos diferencias principales: el contenido interno del espacio está siempre disponible públicamente (los modificadores de acceso no son aplicables en él) y no hay punto y coma después de la llave de cierre.

Añadamos el método move a la clase Shape, que toma como parámetro la estructura Pair:

class Shape
{
public:
   ...
   Shape *move(const Pair &pair)
   {
      coordinates.x += pair.x;
      coordinates.y += pair.y;
      return &this;
   }
};

A continuación, en la función OnStart, puede organizar el desplazamiento de todas las formas en un valor dado invocando esta función:

void OnStart()
{
   //draw a random set of shapes
   for(int i = 0i < 10; ++i)
   {
      Drawing::Shape *shape = addRandomShape();
      // move all shapes
      shape.move(Drawing::Shape::Pair(100100));
      shape.draw();
      delete shape;
   }
}

Tenga en cuenta que los tipos Shape y Pair deben describirse con nombres completos: Drawing::Shape y Drawing::Shape::Pair, respectivamente.

Puede haber varios bloques con el mismo nombre de espacio: todos sus contenidos entrarán en un contexto lógicamente unificado con el nombre especificado.

Los identificadores definidos en el contexto global, en particular todas las funciones integradas de la API de MQL5, también están disponibles a través del operador de selección de contexto no precedido de ninguna notación. Por ejemplo, este es el aspecto que podría tener una llamada a la función Print:

::Print("Done!");

Cuando la llamada se realiza desde cualquier función definida en el contexto global, no es necesaria dicha entrada.

La necesidad puede manifestarse dentro de cualquier clase o estructura si en ellas se define un elemento del mismo nombre (función, variable o constante). Por ejemplo, añadamos el método Print a la clase Shape:

   static void Print(string x)
   {
      // empty
      // (likely will output it to a separate log file later)
   }

Dado que las implementaciones de prueba del método draw en las clases derivadas llaman a Print, ahora se redirigen a este método Print: entre varios identificadores idénticos, el compilador elige el que está definido en un contexto más cercano. En este caso, la definición en la clase base está más cerca de las formas que del contexto global. Como resultado, se suprimirá la salida de registro de las clases de forma.

No obstante, llamar a Print desde la función OnStart sigue funcionando (porque está fuera del contexto de la clase Shape).

void OnStart()
{
   ...
   Print("Done!");
}

Para «arreglar» la impresión de depuración en las clases es necesario preceder todas las llamadas a Print con un operador global de selección de contexto:

class Rectangle : public Shape
{
   ...
   void draw() override
   {
      ::Print("Drawing rectangle"); // reprint via global Print(...)
   }
};