1

I want to implement some hierarchy of classes with polymorphism and I can't make it to work. I have two problems:

  1. The Derived class has extra private variables
  2. The methods of the derived class take as argument an object of the derived class and return an object of the derived class.

I can make this code work, but in a non-polymorphic way. This is the simplified version:

class Base
{
protected:
    int mInt;
public: 
    Base(int iValue) : mInt(iValue) {}
    virtual Base operator+(const Base otherBase)
    {
        Base result( otherBase.mInt + mInt);
        return result;
    }

};

class Derived : public Base
{
private:
    double mDouble;
public:
    Derived(int iValue, double dValue) : Base(iValue) 
   {
        mDouble = dValue;
   }
   Derived operator+(const Derived otherDerived)
   {
        Derived result(otherDerived.mInt + mInt,
                       otherDerived.mDouble + mDouble);
        return result;
   }

};

int main()
{

    Derived DobjectA(2,6.3);
    Derived DobjectB(5,3.1);
    Base* pBaseA = &DobjectA;
    Base* pBaseB = &DobjectB;
    // This does not work
    Derived DobjectC = (*pBaseA)+(*pBaseB);

}

How can I design the classes to make this work?

Msegade
  • 476
  • 7
  • 17
  • 1
    Perhaps this may help http://stackoverflow.com/questions/274626/what-is-object-slicing – Ed Heal Dec 30 '15 at 20:01
  • What do you expect to happen if `pBaseB` actually points to a `Base` object (or any other derived type - except `Derived` - for that matter...)? – Amit Dec 30 '15 at 20:13
  • @Amit I think the best solution will be to throw an exception if the type pointed by pBaseB isn't Derived. – Msegade Dec 30 '15 at 20:22
  • So, essentially, you want an `operator+` that is virtual? [And `operator=` as well, which is a lot harder] – Mats Petersson Dec 30 '15 at 20:22
  • @MatsPetersson Yes, but the arguments and the return type are different in the Derived class, and that gives problems. I was thinking using casts but I don't know how to do it. – Msegade Dec 30 '15 at 20:25
  • Right, so you basically can't do what you want to do. There are ways to do an `operator+` that adds various types, but you can't make a `operator=` that works for "any" type. You will need to think of some other way to achieve that at the fundamental level. Can you perhaps describe the underlying problem you are trying to solve, and we can probably find a different solution that works? [One thought that comes to mind is a pimpl pattern] – Mats Petersson Dec 30 '15 at 20:38
  • @MatsPetersson The problem that I'm trying to solve involves sparse matrices which are a special case of a Matrix. There are a lot of types of storage schemes for sparse matrices: csr,csc cco... which I intend of having one class for each one and all of them inherit from the class Matrix. Every submatrix class has the same operations but different implementations and for example the operator+ is intenden to work like the example above. Also each submatrix has different private members, like the example above. – Msegade Dec 30 '15 at 20:55
  • So a pimpl pattern sounds like the perfect solution for this. I'll write up a simple example. – Mats Petersson Dec 30 '15 at 20:58

2 Answers2

2

The key here is to hide the type of actual object behind the scenes, such that it can be operated on in a meaningful way without knowin the actual type.

We need some way to identify what type of "thing" we actually have:

enum ThingType { TypeA, TypeB } ;

We need a class that is an interface:

class ThingInterface
{
public:
   /* Type of the object */
   virtual ThingType Type() = 0;
   /* Make a copy of ourselves */
   virtual ThingInterface* Clone() = 0;
   /* Add rhs to this */
   virtual void Add(ThingInterface *rhs) = 0;
   virtual ~ThingInterface();
};

Then we need a class that supports our actual operations:

class Thing
{
public:
   /* Create empty object */
   Thing(ThingType type) { ... }
   /* Create copy of existing object */
   Thing(ThingInterface* rhs)
   {
       pImpl = rhs->Clone();
   }

   Thing operator+(const Thing& rhs)
   {
       Thing result(pImpl);
       result.pImpl->Add(rhs.pImpl);
       return result;
   }
private:
   ThingInterface *pImpl;
};

And now we can implement some class to do the different types of thing:

class ThingTypeA: public ThingInterface
{
public:
    ThingTypeA() { ... };
    ThingType Type() { return TypeA; }
    void Clone(ThingInterface *rhs) { ... }
    void Add(ThingInterface *rhs) { ... }
};


class ThingTypeB: public ThingInterface
{
public:
    ThingTypeA() { ... };
    ThingType Type() { return TypeB; }
    void Clone(ThingInterface *rhs) { ... }
    void Add(ThingInterface *rhs) { ... }
};

Obviously, for a matrix implementation, you would need to have some general purpose "get me content of cell X, Y" that is implemented in both ThingTypeA and ThingTypeB - and maybe also something more clever when it comes to figuring out what type the result matrix should be for TypeA + TypeB, etc.

Msegade
  • 476
  • 7
  • 17
Mats Petersson
  • 126,704
  • 14
  • 140
  • 227
  • Nice.. but as you point out yourself, the meaning of `TypeA + TypeB` is a problem. It makes no sense. Trying to gather incompatible types with operations as `+`/`Add`is a design mistake. – Support Ukraine Dec 30 '15 at 21:28
  • No, it's not a design mistake - it's a problem that someone with domain knowledge (in other words, knows how to best figure out sparse matrices). The `Add` function will either have to convert the `rhs` to one that is compatible with itself, or use a virtual function to get the data into the relevant format for a row, column or cell, depending on what complexity on getting the data vs. time overhead is best. – Mats Petersson Dec 30 '15 at 21:37
  • Hmm... Okay.. Well, I must admit that it is a long time ago I took my master which included studies of sparse matrices for electrical engineering so things may have changed since then. Still, I'm convinced that OP is on the wrong track. – Support Ukraine Dec 30 '15 at 21:46
  • Well, the above is the standard solution for "I have several kinds of objects that are kind of similar, but not exactly compatible in all aspects". Whether it is actually right for this case or not, I'm not sure. I know what a sparse matrix is, but I don't know enough to implement one effectively. – Mats Petersson Dec 30 '15 at 22:04
  • Of course, it's not a problem if both types are always the same, just that the whole thing supports several variants. – Mats Petersson Dec 30 '15 at 22:04
  • I won't claim that there are no situation where incompatible types may be well-formed for polymorphism. I still have to see it though - but then again, I still have a lot to learn :-) And I do like your approach for it (+1). Anyway, let's leave it here before we use comments for something that should be in a chat. – Support Ukraine Dec 30 '15 at 22:22
  • Thanks, I think this is the correct approach, but I'm having trouble understanding it. I get that the implementation is separated from the interface, but do I declare the common private variables in ThingInterface and the particular ones in ThingTypeA, ThingTypeB? – Msegade Dec 30 '15 at 23:35
  • 1
    Personally, I think interfaces should be "pure", but you can have interfaces with member common variables. You could also have a `ThingBase : public ThingInterface` and then derive `ThingTypeA` and `ThingTypeB` from `ThingBase`. – Mats Petersson Dec 30 '15 at 23:38
-1

To make the problem line work, you may use a dynamic cast:

Derived DobjectC = *(dynamic_cast<Derived*>(pBaseA)) + *(dynamic_cast<Derived*>(pBaseB));
Nick Ligerakis
  • 301
  • 4
  • 7
  • Ok, that works. But that doesn't solve the problem of polymorphism. I can't use a Base class pointer in any place where a Derived class can be. – Msegade Dec 30 '15 at 20:45
  • The result of `dynamic_cast` is not guaranteed to be a valid pointer- it may be nullptr, which you are cheerily dereferencing without a care in the world here. – Puppy Dec 30 '15 at 20:46
  • @Puppy Yes, of course you're right. But I wasn't trying to answer a question on the semantics of dynamic_cast, as that would be another question altogether. In the context of this question, the solution compiles and runs as expected. – Nick Ligerakis Dec 30 '15 at 20:52
  • The semantics of dynamic_cast are not another question altogether, they are the essence of your answer, which is cripplingly flawed as a result of their fundamental mis-fit to this situation. The fact that it happens to work in this trivial case bears no meaning on the suitability of the answer in the general case. – Puppy Dec 30 '15 at 21:01
  • Correct - but not a solution for OP – Support Ukraine Dec 30 '15 at 21:07