Thread: operator overloading with abstract base class

  1. #1
    Registered User
    Join Date
    Feb 2003
    Posts
    596

    operator overloading with abstract base class

    I've been pondering this design pattern for a few days & getting nowhere. Please have a look at this "skeleton" code which comes from Linxutopia - Thinking in C++ - 15: Polymorphism & Virtual Functions - Operator overloading:

    It doesn't show the actual arithmetic implementation but gives the outline of the class hierarchy.
    Code:
        //: 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;
            }
        } ///:~
    I understand the need for the multiple dispatching and how it works. What bothers me is the return value of the * operator. When main calls it, as in
    Code:
    m1 * m2
    it will call m1's * operator, giving it m2 as the rhs argument. That * function in turn calls m2's multiply function, giving it m1 as the rhs argument. The multiply function modifies the value of m2's data member(s) and returns a reference to m2 to the * function, which then returns that reference to main. So the result of the * operation is to modify one of the operands.

    Similarly, it seems that using this pattern to implement an assignment operator would modify the rvalue of the assignment.

    This does not seem right to me. A + operator shouldn't modify one of the operands, and an = operator shouldn't modify the rvalue. On the other hand, I don't see any way to get around this. Is this just an inappropriate application for polymorphism, or am I missing something?

  2. #2
    Registered User
    Join Date
    Jun 2005
    Posts
    6,815
    I suspect this is an attempt to use the visitor pattern, gone astray. The quality of Bruce Eckel's "Thinking in C++" (which is what the pages you link to are from, if you dig back to the "front matter") is known to be pretty patchy. You've hit on a patch where the quality is (how can I say this gently?) less than it might be.

    You're right that operators like operator+ or operator* would not generally be expected to modify their arguments (the arguments, if references, would normally be const). They would also not generally be expected to return a reference: they would normally return a dictinct object containing the result (eg operator+() will return an object containing the sum of the arguments, and those arguments will not change).

    Similarly, an assignment operator does not usually change the rvalue.

    There are exceptions to my statements above but, for most classes, a guideline of the form "operators behave as you'd expect of an int type" would apply. You'd expect an operator*() to do something called "multiplication of the operands" rather than running a tap-dancing simulation, for example. Multiplying two ints does not change the ints being multiplied, and gives a distinct int value containing the result. Assigning one value to the other does not modify the right hand operand.
    Last edited by grumpy; 12-04-2010 at 12:53 AM.
    Right 98% of the time, and don't care about the other 3%.

    If I seem grumpy or unhelpful in reply to you, or tell you you need to demonstrate more effort before you can expect help, it is likely you deserve it. Suck it up, Buttercup, and read this, this, and this before posting again.

  3. #3
    Registered User
    Join Date
    Feb 2003
    Posts
    596
    OK but you're basically just repeating my objection. The operator can't return a base class object if the base is abstract. And I can't see how to return a derived class object and still preserve the polymorphism. Can you point me to something that explains the correct way to do this?

  4. #4
    Registered User
    Join Date
    Jun 2005
    Posts
    6,815
    That's right. I repeated your objection.

    Generally (at least for basic operations like addition and multiplication) the correct method is to do this sort of thing in the most derived (aka leaf) classes. In other words, you won't do it polymorphically - you will do it in a concrete manner with operators that work with the derived classes, not through some magic inherited from the base class. Any such magic implies a need for the base class to depend on the derived class - which means that the base class needs to be updated every time you add support of a new derived class. Every time the base class is updated, it is necessary to check that ALL derived classes still work as intended. Which means that, every time any derived class changes, it is necessary to revalidate the correct behaviour of all classes in the hierarchy. That means the entire class hierarchy is fragile and hard to maintain.

    As an example of doing things in a manner that affects only particular concrete classes, we know that multiplication of a Scalar and a Vector will yield a Vector, so provide a "Vector operator*(const Scalar &, const Vector &)" and a "Vector operator*(const Vector &, const Scalar &)".

    That means you will need to write one operator function for every combination of types you wish to add/multiply, etc. If you wish to have code reuse, look for combinations where those operators may call each other. For example, the "Vector operator*(const Vector &, const Scalar &)" might call the "Vector operator*(const Scalar &, const Vector &)".
    Right 98% of the time, and don't care about the other 3%.

    If I seem grumpy or unhelpful in reply to you, or tell you you need to demonstrate more effort before you can expect help, it is likely you deserve it. Suck it up, Buttercup, and read this, this, and this before posting again.

  5. #5
    Registered User
    Join Date
    Feb 2003
    Posts
    596
    Thanks, I suspected that the answer would be something like that. I had been thinking about putting methods in the base class with derived class return types, but it just didn't "feel" right for exactly the same reason that you described. I just needed to hear it from someone with experience.

    In the interim I realized that the assignment operator doesn't present this problem since it should return a reference, and see that I can easily implement it without altering the rvalue.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. derived class can not access base class protected member?
    By George2 in forum C++ Programming
    Replies: 2
    Last Post: 10-21-2007, 06:32 PM
  2. Defining derivated class problem
    By mikahell in forum C++ Programming
    Replies: 9
    Last Post: 08-22-2007, 02:46 PM
  3. Creating a database
    By Shamino in forum Game Programming
    Replies: 19
    Last Post: 06-10-2007, 01:09 PM
  4. Abstract Base Class and References
    By Thantos in forum C++ Programming
    Replies: 9
    Last Post: 10-13-2004, 01:35 PM
  5. structure vs class
    By sana in forum C++ Programming
    Replies: 13
    Last Post: 12-02-2002, 07:18 AM