15.11. Sobrecarga de operadores

Se pueden crear operadores virtuales de forma análoga a otras funciones miembro. Sin embargo implementar operadores virtuales se vuelve a menudo confuso porque se está operando sobre dos objetos, ambos sin tipos conocidos. Esto suele ser el caso de los componentes matemáticos (para los cuales se suele usar la sobrecarga de operadores). Por ejemplo, considere un sistema que usa matrices, vectores y valores escalares, todos ellos heredados de la clase Math:

//: C15:OperatorPolymorphism.cpp
// Polymorphism with overloaded operators
#include <iostream>
using namespace std;

class Matrix;
class Scalar;
class Vector;

class Math {
public:
  virtual Math& operator*(Math& rv) = 0;
  virtual Math& multiply(Matrix*) = 0;
  virtual Math& multiply(Scalar*) = 0;
  virtual Math& multiply(Vector*) = 0;
  virtual ~Math() {}
};

class Matrix : public Math {
public:
  Math& operator*(Math& rv) {
    return rv.multiply(this); // 2nd dispatch
  }
  Math& multiply(Matrix*) {
    cout << "Matrix * Matrix" << endl;
    return *this;
  }
  Math& multiply(Scalar*) {
    cout << "Scalar * Matrix" << endl;
    return *this;
  }
  Math& multiply(Vector*) {
    cout << "Vector * Matrix" << endl;
    return *this;
  }
};

class Scalar : public Math  {
public:
  Math& operator*(Math& rv) {
    return rv.multiply(this); // 2nd dispatch
  }
  Math& multiply(Matrix*) {
    cout << "Matrix * Scalar" << endl;
    return *this;
  }
  Math& multiply(Scalar*) {
    cout << "Scalar * Scalar" << endl;
    return *this;
  }
  Math& multiply(Vector*) {
    cout << "Vector * Scalar" << endl;
    return *this;
  }
};

class Vector : public Math  {
public:
  Math& operator*(Math& rv) {
    return rv.multiply(this); // 2nd dispatch
  }
  Math& multiply(Matrix*) {
    cout << "Matrix * Vector" << endl;
    return *this;
  }
  Math& multiply(Scalar*) {
    cout << "Scalar * Vector" << endl;
    return *this;
  }
  Math& multiply(Vector*) {
    cout << "Vector * Vector" << endl;
    return *this;
  }
};

int main() {
  Matrix m; Vector v; Scalar s;
  Math* math[] = { &m, &v, &s };
  for(int i = 0; i < 3; i++)
    for(int j = 0; j < 3; j++) {
      Math& m1 = *math[i];
      Math& m2 = *math[j];
      m1 * m2;
    }
} ///:~

Listado 15.18. C15/OperatorPolymorphism.cpp


Para simplificar sólo se ha sobrecargado el operator*. El objetivo es ser capaz de multiplicar dos objetos Math cualquiera y producir el resultado deseado - hay que darse cuenta que multiplicar una matriz por un vector es una operación totalmente distinta a la de multiplicar un vector por una matriz.

El problema es que, en el main(), la expresión m1 * m2 contiene dos referencias Math, y son dos objetos de tipo desconocido. Una función virtual es sólo capaz de hacer una única llamada - es decir, determinar el tipo de un único objeto. Para determinar ambos tipos en este ejemplo se usa una técnica conocida como despachado múltiple (multiple dispatching), donde lo que parece ser una única llamada a una función virtual se convierte en una segunda llamada a una función virtual. Cuando la segunda llamada se ha ejecutado, ya se han determinado ambos tipos de objetos y se puede ejecutar la actividad de forma correcta. En un principio no es transparante, pero después de un rato mirando el código empieza a cobrar sentido. Esta materia es tratada con más profundidad en el capítulo de los patrones de diseño en el Volumen 2 que se puede bajar de >www.BruceEckel.com.