Disponible la nueva versión "donationware" 7.3 de OrganiZATOR
Descubre un nuevo concepto en el manejo de la información.
La mejor ayuda para sobrevivir en la moderna jungla de datos la tienes aquí.

Curso C++

[Home]  [Inicio]  [Índice]


Sig.

4.11.2d3   Iniciar miembros (I)

§1  Sinopsis

Hemos señalado ( 4.11.2d1), que la misión de los constructores es la correcta iniciación de los miembros cuando se crean objetos de la clase.  Esta iniciación puede hacerse de varias formas, que en algunos casos pueden coexistir. Tres de ellas se utilizan para instanciar objetos.  La última es un recurso a utilizar en la definición de la clase.

  • Asignación directa al instanciar el objeto
  • Lista de iniciadores al instanciar el objeto
  • Invocación implícita o explícita al constructor con una lista de argumentos al instanciar el objeto
  • Lista de Iniciadores en el constructor de la clase  (Pág. siguiente  )
§2  Asignación directa

Es el caso siguiente:

class X {
  static int is;
  public:
  int i;
  char c;
};               // El compilador proporciona un constructor por defecto
 
int X::is = 33;  // L.8: Solo con propiedades estáticas!!
...
int main() {     // ========
  X x;           // L.9: invocación implícita al constructor por defecto
  x.i = 1;       // asignación directa de miembro
  x.c = 'a';     // Ídem
  ...
}


Este sistema es permitido, aunque muestra una técnica de programación deficiente, pues todas las variables deben ser públicas y viola el principio de encapsulación. La expresión de L.9 exige que haya un constructor por defecto (puede ser el proporcionado automáticamente por el compilador si no se ha definido algún otro de forma explícita) [2].

Recordemos que la asignación de L.8 es un caso especialísimo. Solo es posible con variables miembro estáticas (sean públicas, protegidas o privadas) y que este tipo de asignación es posible incluso antes de haber instanciado ningún objeto concreto de la clase ( 4.11.7).

§3  Lista de iniciadores

También es permitida una variación sintáctica de la anterior aunque más compacta:

X x = {1, 'a'};  // invocación implícita al constructor + asignación

Este tipo de iniciación, que utiliza una lista de iniciadores entre corchetes (como en el caso de las matrices), exige que todas las variables sean públicas, ninguna e ellas sea una clase, y no se haya definido un constructor explícito (es el caso típico de las estructuras 4.5.2) [1]. Los iniciadores de la lista deben estar en el mismo orden en que aparecen en la definición de la clase.

§3.1  Ejemplo:

#include <iostream.h>
class Punto {
  public: int x; int y;
};
int main() {         // ===================
  Punto p = {1,2};
  cout << "Valor p.x = " << p.x << endl;
}

Salida:

Valor p.x = 1


§3.2  Esta técnica es es válida incluso con clases anidadas siempre que cumplan con las condiciones señaladas:

#include <iostream>
using namespace std;

class Vector {
  public: float x, y;
  class Punto {
    public: int x, y;
  };
  Punto pa;
};

void main() {       // ============
  Vector v1 = { 2.0, 3.0, {4, 5}};
  Vector v2 = { 2.0, 3.0, 4, 5 };     // Ok. equivalente al anterior
  cout << "V1.x = " << v1.x << endl;
  cout << "V1.y = " << v1.y << endl;
  cout << "V1.pa.x = " << v1.pa.x << endl;
  cout << "V1.pa.y = " << v1.pa.y << endl;
}

Salida:

V1.x = 2
V1.y = 3
V1.pa.x = 4
V1.pa.y = 5


§3.3  Sin embargo esta técnica no es válida si las variables de la lista son instancias de una clase. Ejemplo:

class Punto { public: int x, y; };
class Triangulo {
  public: Punto p1, p2, p3;
};
...
Punto p1 = {1, 2}, p2 = {3, 4}, p3 = {5, 6}; // Ok.
Triangulo t1 = { p1, p2, p3};                // Error!!
Triangulo t2 = {{1, 2},{3, 4},{5, 6}};       // Ok.

§4  Lista de argumentos

Si la clase tiene un constructor explícito, puede utilizarse una invocación implícita o explícita al constructor con una lista de argumentos entre paréntesis, como se muestra en el ejemplo.

class X {
  int i;         // privadas por defecto
  char c;
  public:        // a partir de aquí todos los miembros son públicos
  X(int entero, char caracter){      // Constructor explícito
    i = entero;
    c = caracter;
  };
};
...

En este caso, caben dos sintaxis para invocar al constructor (ambas con argumentos):

X x(1, 'a');        // invocación implícita
X x = X(1, 'a');    // invocación explícita

Observe que en este caso no sería ya posible utilizar la iniciación del ejemplo anterior , ya que existe un constructor explícito:

X x = {1, 'a'};     // Error:

Observe también la definición del constructor.  Como función-miembro de clase, tiene acceso directo a todas sus variables, incluso las privadas, por lo que en su cuerpo podemos referirnos directamente a ellas. Es decir, podemos utilizar las expresiones:

    i = entero;
    c = caracter;

en vez de:

    X::i = entero;
    X::c = caracter;

que serían equivalentes aunque innecesarias. Esta capacidad permanece incluso si la definición del constructor (o cualquier otro método) se ha sacado fuera del cuerpo de la definición de la clase. Es decir, aunque hubiese sido:

class X {
  int i;         // privadas por defecto
  char c;
  public:        // a partir de aquí todos los miembros son públicos
  X(int, char);  // Prototipo de Constructor
};
 
X::X(int entero, char caracter){ // Definición del constructor
   i = entero;
   c = caracter;
};
...

§4.1  Ejemplo

#include <iostream.h>

class C {
  public:
  int x; int y;
  C (int i, int j) { x = i; y = j; }   // Constructor
};

int main() {          // =============
// C c = {1,2};          Error!! forma no permitida aquí
   C c = C(1,2);      // Ok. invocación explícita al constructor
   cout << "Valor c.x = " << c.x << endl;
}

Salida:

Valor c.x = 1

§5  Paso de argumentos y sentencias de asignación en el cuerpo del constructor

La iniciación de miembros puede realizarse mediante asignaciones en el cuerpo del constructor. Puede disponerse que este acepte determinados argumentos que determinarán los valores de inicio, pero entonces es preciso disponer de valores por defecto para que pueda ser utilizado como constructor por defecto. Es el caso del siguiente ejemplo:

class X {
  int i;
  char c;      
  public:       // a partir de aquí todos los miembros son públicos
  X(int entero = 0, char caracter = 0){ // C1: constructor-1
    i = entero;
    c = caracter;
  };
  X(const X& obj) {      // C2: constructor-2
    X* ptr = new X;
    ptr.i = obj.i;
    ptr.c = obj.c;
  };
};

Comentario

Para exponer la casuística de forma más completa, hemos definido dos constructores para la clase del ejemplo. Al primero le asignamos argumentos por defecto, la razón es que pueda servir como constructor por defecto, pues el operador new del cuerpo del segundo necesita que exista un constructor por defecto definido en la clase ( 4.9.20).

El funcionamiento del segundo es un tanto especial; acepta una referencia a un objeto de la propia clase. Crea un objeto nuevo del tipo de la clase con new, e inicia sus elementos copiando de sus homónimos correspondientes. Veremos que, debido a su forma especial de trabajar, este tipo de constructor se denomina constructor copiador o sencillamente constructor-copia ( 4.11.2d4).

Una vez definida la clase y sus constructores, más tarde pueden ser llamados con los parámetros adecuados para construir un objeto inicializado a los valores deseados:

X* ptr;             // declara ptr puntero-a-tipo-X
ptr–>X::X(3, 'a');  // invocación explícita a C1 mediante puntero
X x = X(4, 'b');    // invocación explícita a C1
X y(5, 'c');        // invocación implícita a C1
X z;                // invocación implícita a C1

Las tres primeras formas de invocar al constructor le pasan argumentos, la última utiliza los argumentos por defecto y todas son igualmente correctas.  Observe que en la primera tenemos un objeto anónimo, solo puede ser accedido a través de su puntero. La tercera puede enunciarse diciendo: las clases con constructor pueden ser inicializadas con una lista de expresiones entre paréntesis; expresiones que actúan como argumentos de una invocación implícita del constructor.

El segundo constructor C2 , puede servir igualmente a nuestros fines, solo que ahora debemos pasarle como argumento una referencia a un objeto de la clase. Por ejemplo:

X uno = X(x);    // invocación implícita a C2
X dos = y;       // invocación implícita a C2
X bis(z);        // invocación implícita a C2

§5.1  Ejemplo

Todo lo anterior en un código funcional:

#include <iostream.h>

class X {
  public:
  int i;
  char c;
  X(int = 0, char = NULL);        // C1: prototipo (constructor por defecto)
  X(const X&);                    // C2: prototipo (constructor copia)
};
X::X(int entero, char caracter) { // definición de C1
   i = entero;
   c = caracter;
}
X::X(const X& obj) {     // definición de C2
   X* ptr = new X;
   ptr->i = obj.i;
   ptr->c = obj.c;
}

void main() {            // =========================
  X * ptr;
  ptr = &X::X(1, 'a');   // se crean tres objetos
  X x = X(2, 'b');
  X y(3, 'c');
 
  cout << "Valor ?: " << ptr->i << ", " << ptr->c << endl;
  cout << "Valor x: " << x.i << ", " << x.c << endl;
  cout << "Valor y: " << y.i << ", " << y.c << endl;
 
  X uno = X(x);    // invocación implícita a C2
  X dos = y;       // invocación implícita a C2
  X bis(z);        // invocación implícita a C2
 
  cout << "Valor uno: " << uno.i << ", " << uno.c << endl;
  cout << "Valor dos: " << dos.i << ", " << dos.c << endl;
  cout << "Valor bis: " << bis.i << ", " << bis.c << endl;
}

Salida:

Valor: 1, a
Valor: 2, b
Valor: 3, c

Comentario

Observe que se ha sacado la definición de los constructores C1 y C2 fuera del cuerpo de la clase (dejamos en esta solo el prototipo) utilizando una propiedad de todos los métodos: la definición puede estar fuera del cuerpo de la propia clase si se añade el especificador adecuado.

  Inicio.


[1]  Este tipo de iniciación no es posible si la clase es virtual.  Es decir, si el objeto es polimórfico.

[2]  Ver al respecto la nota en 4.11.2d4.

Sig.