c ++重载虚拟+运算符

时间:2012-04-17 06:18:45

标签: c++ operator-overloading virtual

#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;
}

输出: 2;2然后它在第b3 = dynamic_cast<bb&>(a4);行崩溃,发出错误std::bad_cast at memory location 0x0012fdbc..

我发现的原因是a2 + a3的表达式的结果作为aa类型的对象,并且在下一个语句中我们试图将它转换为无效的派生类型对象,因此导致异常。所以我的查询是否可以实现上述意图,即aa &a4 = a2 + a3;,并对上述函数进行一些更改?

3 个答案:

答案 0 :(得分:3)

C ++有一个调度(基于虚函数)。 多态二元运算符不可避免地陷入“双重调度”的情况,因此不能通过简单的虚函数实现多态。

此外,由于您返回的值,每个子类信息都会丢失。

处理这种情况的一种更恰当的方法是定义一个非多态句柄来实现操作并保存多态类型,并将操作执行委托给它们。

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;
};

答案 1 :(得分:2)

在派生类中直接重载aa operator +(aa& obj)毫无意义。

即使虚拟,您现在也会调用一个随时返回aa的方法,因此a2 + a3的结果将是aa对象,而不是bb对象。它构造为bb对象,但复制到生成的aa对象中,副本丢失bb信息。

您应该做的是覆盖bb operator +(const bb& obj)的特定版本,请注意,这会隐藏原始operator+ aa参数,因此您仍需要重新定义或导入与the using statement

现在,这些是更好的语义,但它不会解决您的问题,因为在您的情况下,它仍将调用返回operator+对象的aa。在这种情况下,如果你真的想要解决这个问题,你应该在添加它们之前向下投射a2a3

但实际上,如果您在具有虚拟化功能的父子系统中进行此类操作,您可能会重新考虑您的设计。通常,在任何情况下使用dynamic_cast都会引起一些人的注意。它当然有它的用途,但应尽可能避免再次向下倾斜。

答案 2 :(得分:0)

你正在传递一个基类,为什么要将它转换为派生类型?您应该从转换中捕获异常,因为您知道类型不是您所期望的。

请注意,虽然a1a2的实例引用了bba4,但{{1}}却没有。这就是前两个演员没有抛出异常的原因。