【问题标题】:Why does a derived class call the base classes operator instead of its own operator为什么派生类调用基类运算符而不是自己的运算符
【发布时间】:2012-01-07 18:49:42
【问题描述】:

在这个例子中我应该怎么做才能触发正确的操作符:

#include<iostream>
using namespace std;

class A
{
public:
    virtual void operator/(A& a){cout << "class A";}
};
class B : public A
{
public:
    void operator/(B& b){cout << "class B";}
};

int main()
{
    A* a = new B;
    A* b = new B;
    *a / *b;
    return 0;
}

输出是"class A",但应该是"class B"。
我该如何解决这个问题?谢谢。

编辑:根据答案...我应该为每个派生类执行此操作:

class A
{
public:
    virtual void operator/(A& a){cout << "class A";}
};
class B : public A
{
public:
    virtual void operator/(A& a) override {cout << "class B";}
};
class C : public B
{
public:
    virtual void operator/(A& a) override {cout << "class C";}
};
class D: public C
{
public:
    void operator/(A& a) override {cout << "class D";}
};
int main()
{
    A* a = new D;
    A* b = new D;
    *a / *b;
    //...
}

【问题讨论】:

  • 我认为您提出的答案仍然是错误的。我认为你应该专注于澄清这个问题。例如,您想打印“class X”,其中 X 是与 both 操作数兼容的最派生类。这是你想说的吗?
  • @Aaron 例如:指向 A 类的指针是指向 X 类的指针(大多数派生) A* a = new X;这就是我感到困惑的地方......意味着我们必须永远覆盖所有类,因为指向类 A 的指针有时可能是指向同一个 porgram 中的 B 的指针...... A* a = new B;
  • 请专注于阐明你的目标。给定 *x / *y,您是否希望确定与这两个操作数兼容的最派生类?一旦你明确了你的目标,我们都可以尝试解决它。​​
  • 致所有相关人员。我认为我们面临三个任务。首先:在此处确定最终的目标 - 定义(用简单的英语)所需的行为。第二:解释为什么问题中的代码未能达到预期的行为。第三:编写正确的代码。
  • 我的目标很简单:“有没有其他方法可以在我编辑的问题中以更短的方式或更有效的方式实现相同的目标”。谢谢。 @Aaron我想划分类new D和new D,它们的指针指向A类(大多数基类)

标签: c++ inheritance operator-keyword


【解决方案1】:

两个原因:

  • B::operator/(B&amp;) 不是对A::operator/(A&amp;) 的覆盖,因此它永远不会在多态情况下被调用。
  • B::operator/ 以B&amp; 为参数,但*b 的静态类型为A。

【讨论】:

  • 所以在例如 5 个类的多重继承中,我必须为每个基类和子基类覆盖每个 operotr?
  • 只有第一个原因是相关的,真的。由于*a 的静态类型是A,因此甚至不会考虑另一个运算符进行重载解析。
  • @Charles:我列出了第二个原因,因为第一个问题可以通过添加virtual void A::operator/(B&amp;) 函数来“修复”...
  • 这是一个很好的例子,说明了许多语言(包括 C++ 和 Java)中的多态性非常烦人的限制。理想情况下,它不仅仅是this 类型的多态。继承到B 的A::foo 版本理想情况下是“类型”operator/ (B&amp; this, B&amp; x);,而不是当前的operator/ (B&amp; this, A&amp; x);。其他语言,例如 Haskell(在一定程度上)对此有更灵活的想法。
  • @Aaron:你说的是double dispatch,对吧?
【解决方案2】:

(首先,我将尝试解释目前正在发生的事情,但最后我会尝试编写一个“正确”的程序。)

我将使用x 和y 而不是a 和b - 不会那么混乱。

A* x = new C;
A* y = new B;
*x / *y;

x 的 静态类型 是 A - 也就是说,这就是变量的类型。 动态类型,变量指向的对象的真实类型,是C。

y 的 静态类型 是 A - 也就是说,这就是变量的类型。 动态类型,变量指向的对象的真实类型,是B。

所以这里似乎涉及四种类型。但实际上,y 的动态类型从不相关,正如我们将看到的。这样就剩下(至多)三种相关类型。为了理解这一点,我们将表达式改写如下:

x -> operator/ (*y);

首先,编译器查看x 的静态类型。在这种情况下,它是A。然后它在具有适当签名的静态类型中查找方法。 “适当的签名”是什么意思?答案是方法查找忽略了变量的动态类型。 y 的静态类型为 A,因此选择了 operator/(A&amp;)。 (我们知道y 确实是B,但这被忽略了。)最后,运行时会查看x 的动态类型(忽略y)。动态类型为C。因为 A 中的 operator/(A&amp;) 是 virtual,所以运行时实际上会调用 C::operator/(A&amp;)。

简而言之,y 的动态类型不相关。永远不会调用 operator/(B&amp;) 或 operator/(C&amp;) 方法。应该删除这些方法。

这里有一个基本的不对称性。在*y / *x 中,y 的动态类型是相关的,而不是 x 的动态类型。

如何解决

在“解决”问题之前,我们需要确保我们知道问题所在。因此,我将首先澄清我的解释。如果这是错误的解释,请提前道歉。

我将假设提问者打算x 的动态类型 将始终与y 的动态类型 相同,并且如果它们不同,那么应该有一条错误消息。例如,这段代码应该可以工作:

A * x = new A;
A * y = new A;
*x / *y; // divide an instance of A by another instance of A

和

A * x = new B;
A * y = new B;
*x / *y; // divide an instance of B by another instance of B

和

A * x = new C;
A * y = new C;
*x / *y; // divide an instance of C by another instance of C

但不是

A * x = new B;
A * y = new C;
*x / *y; // should give an error

或

A * x = new C;
A * y = new B;
*x / *y; // should give an error

这是一个完整的程序:

#include <iostream>
#include <typeinfo>
#include <cassert>
using namespace std;
class A
{
public:
    virtual void operator/(A& z) {
            assert(typeid(z) == typeid(*this));
            cout << "class A" << endl;
    }
};
class B : public A
{
public:
    virtual void operator/(A& z) {
            assert(typeid(z) == typeid(*this));
            cout << "class B" << endl;
            B& b = dynamic_cast<B&>(z);
    }
};
class C : public B
{
public:
    virtual void operator/(A& z) {
            assert(typeid(z) == typeid(*this));
            cout << "class C" << endl;
            C& c = dynamic_cast<C&>(z);
    }
};
int main()
{
    A* x = new C;
    A* y = new C;
    *x / *y;
}

B 和 C 中的操作符做了两件事:

  • 测试第二个操作数的类型是否符合预期。即确认/ 两边的类型相同。
  • 将A&amp; 转换为适当的类型,允许除法代码对两个操作数具有完全访问权限

【讨论】:

  • 非常感谢您的解释,也感谢 cmets 也很有价值!哦... tnx 也用于编辑:D 我现在看到了
【解决方案3】:

B::operator/ 不是A::operator/ 的覆盖,因为它们的参数不同。

只有在签名兼容的情况下,您才能覆盖虚拟函数(或运算符)。

【讨论】:

    【解决方案4】:

    您实际上并没有从A 覆盖operator/,因为B::operator/ 与A::operator/ 具有不同的方法签名(参数类型为B&amp; 而不是A&amp;)。因此,您对A&amp; 类型的对象调用operator/ 将调用类A 的唯一现有operator/,它可能是virtual,但不会在class B 中被覆盖。因此,您需要做的是将 B 的 operator/ 更改为

        void operator/(A& b){std::cout << "class B"<<std::endl;}
    

    请注意,*b 无论如何都不能用作B::operator/ 的参数,因为它属于A&amp; 类型而不是B&amp; 类型。

    【讨论】:

      【解决方案5】:

      运算符重载是使用静态类型解决编译时间问题,并且只有一个重载(A 类中的那个)可以处理 A(*b 对编译器来说是 A)

      【讨论】:

        猜你喜欢
        • 2021-07-03
        • 2019-09-02
        • 1970-01-01
        • 2012-08-22
        • 2012-06-05
        • 1970-01-01
        • 2012-02-12
        • 2016-07-10
        • 1970-01-01
        相关资源
        最近更新 更多