Jugal Kishore
Jugal Kishore

Reputation: 33

c++ overloading virtual + operator

#include <iostream>

class aa
{
public:
    aa(){}

    aa(aa& obj)
    {
        aa1 = obj.aa1;
    }

    virtual aa operator =(aa& obj)
    {
        aa1 = obj.aa1;
        return (*this);
    }

    virtual aa operator +(aa& obj)
    {
        aa1 += obj.aa1;
        return (*this);
    }

    int aa1;
};

class bb: public aa
{
public:
    bb():aa(){}

    bb(bb& obj)
    {
        bb1 = obj.bb1;
    }

    aa operator =(aa& obj)
    {
        aa::operator =(obj);
        bb b1 = dynamic_cast<bb&>(obj);
        bb1 = b1.bb1;       
        return (*this);
    }

    aa operator +(aa& obj)
    {
        aa::operator +(obj);
        bb b1 = dynamic_cast<bb&>(obj);
        bb1 += b1.bb1;
        return (*this);
    }

    int bb1;
};


int main()
{
    bb b1;
    bb b2;

    b1.bb1 = 1;
    b1.aa1 = 1;

    b2.bb1 = 2;
    b2.aa1 = 2;

    aa &a1 = b1;
    aa &a2 = b2;

    a1 = a2;
    b1 = dynamic_cast<bb&>(a1);
    b2 = dynamic_cast<bb&>(a2);

    std::cout<<b1.aa1<<";"<<b1.bb1;

    bb b3;
    b3.bb1 = 3;
    b3.aa1 = 3;

    aa &a3 = b3;

    aa &a4 = a2 + a3;
    b3 = dynamic_cast<bb&>(a4);

    return 0;
}

Output: 2;2 and then it crashes at line b3 = dynamic_cast<bb&>(a4); giving the error std::bad_cast at memory location 0x0012fdbc..

The reason I have found is the result of expression of a2+a3 is coming as object of type aa, and in the next statement we are trying to cast it to derived type object which is not valid, hence leading to exception. So my query is can we achieve the above intention i.e. aa &a4 = a2 + a3; with some changes in the above functions?

Upvotes: 2

Views: 7728

Answers (3)

Emilio Garavaglia
Emilio Garavaglia

Reputation: 20740

C++ has a single dispatch (based on virtual function). Polymorphic binary operators fall inevitably into the "dual dispatch" case, and hence cannot be implemented polymorphicaly by means of just simple virtual function.

Also, since you are returning a value, every sub-classes information is lost.

A more proper way to handle this kind of situation is to define a non-polymorphic handle that implements the operations and holds polymorphic types, delegating to them the operation execution.

Like

class handle
{
public:
    class aa;
    class bb;

    class root
    {
    public:
        virtual ~root() {}         //< required being this polymorphic
        virtual root* clone()=0;

        virtual handle add_invoke(const root& r) const=0; //resolve the 2nd argument
        virtual handle add(const aa& a) const=0;    //resolve the 1st argument
        virtual handle add(const bb& a) const=0;    //resolve the 1st argument
    };

    class aa: public root
    {
    public:
        aa(...) { /*set vith a value */ }
        aa(const aa& a) { /* copy */ }
        virtual root* clone() { return new aa(*this); }

        virtual handle add_invoke(const root& r) const 
        { return r.add(*this); }  //will call add(const aa&);

        virtual handle add(const aa& a) const
        { return handle(new aa(.../*new value for aa with (aa,aa)*/)); }
        virtual handle add(const bb& a) const
        { return handle(new bb(.../*new value for bb with(aa,bb)*/)); }
    };

    class bb: public root
    {
    public:
        bb(...) { /*set vith a value */ }
        bb(const bb& b) { /* copy */ }
        virtual root* clone() { return new bb(*this); }

        virtual handle add_invoke(const root& r) const
        { return r.add(*this); }  //will call add(const bb&);

        virtual handle add(const aa& a) const
        { return handle(new bb(.../*new value for aa with (bb,aa)*/)); }
        virtual handle add(const bb& a) const
        { return handle(new bb(.../*new value for bb with (bb,bb)*/)); }
    };

    handle() :ps() {}
    //support both copy (by clone) and move (by stole)
    handle(const handle& s) :ps(s.ps? s.ps->clone(): nullptr) {}
    handle(handle&& s) :ps(s.ps) { s.ps=nullptr; };
    //assign by value, to force temporary assign
    handle& operator=(handle h) { delete ps; ps=h.ps; h.ps=0; return *this; }
    //cleanup
    ~handle() { delete ps; }

    //the operator+
    friend handle operator+(const handle& a, const handle& b)
    { 
        return (b.ps && a.ps)? b.ps->add_invoke(*a.ps): handle(); 
        //Note: manage also the a+b with one of `a` or `b` as null, if it make sense
    }

private:
    handle(root* p) :ps(p) {}

    root* ps;
};

Upvotes: 3

KillianDS
KillianDS

Reputation: 17186

Literally overloading aa operator +(aa& obj) in the derived class makes little sense.

Even when virtual you will now call a method that returns aa at any time, so the result of a2 + a3 will be an aa object, never a bb object. It is constructed as a bb object, but copied into the resulting aaobject, and a copy loses the bbinformation.

What you should do is override a specific version for bb operator +(const bb& obj), note that this will however hide the original operator+ for aa arguments so you'll still have to redefine it or import with the using statement.

Now, these are better semantics but it will not solve your problem, as in your case it will still call the operator+ that returns an aa object. In this case, if you really want to solve this, you should down cast a2and a3before adding them.

But really, if you are doing such things in a parent-child system with virtualized functions you might rethink your design. Usually, use of a dynamic_cast should raise some eyebrows in any case. It certainly has its uses, but downcasting again should be avoided as much as possible.

Upvotes: 2

littleadv
littleadv

Reputation: 20272

You're passing a base class, why would it cast it to a derived cast? You should catch exceptions from the casting, because that's how you know the type is not what you expected.

Note that while a1 and a2 are referenced to instances of bb, a4 is not. That's why the first two casts didn't throw an exception.

Upvotes: 0

Related Questions