如果派生类重写基类方法,为什么要调用基类方法

Why is the base class method called if the derived class overrides the method?

本文关键字:类方法 基类 为什么 调用 派生 重写 如果      更新时间:2023-10-16

考虑以下程序:

class Base {
public:
    virtual void foo() const {
        cout << "Base::foo()" << endl;
    }
};
class Derived : public Base {
public:
    virtual void foo() {
        cout << "Derived::foo()" << endl;
    }
};
void func(Base& obj) {
    obj.foo();
}
void main() {
    Derived d;
    func(d); // Base::foo() is printed 
}

如果我从Basefoo方法中移除const,则调用Derived::foo()。我似乎无法理解这种行为。

1) 这种行为的原因是什么?

2) 这是在编译时还是在运行时决定的?

感谢

在派生类中,函数签名如下:

virtual void foo(); //Derived::foo

其中没有提到CCD_ 5。它是一个非常数成员函数,而Base::foo是一个常量成员函数。它们是两个不同的函数,因为const是函数签名的一部分。

virtual void foo() const; //Base::foo 

派生类并没有覆盖这个函数,而是添加了另一个函数。

因此,解决方案是:

class Derived : public Base {
public:
    virtual void foo() const {
        cout << "Derived::foo()" << endl;
    }
};

由于const是函数签名的一部分。所以当您打算覆盖base的foo时,必须提到它。


@davka问:

那么,为什么选择常量版本而不是非常量版本呢?有什么规则吗?或者这只是第一个选项?

这是因为obj静态类型是Base,并且函数名称是根据对象的静态类型解析的。Base甚至没有非常量版本。因此,它不存在被选中或被拒绝的问题。它一开始并不存在于Base中。

void func(Base& obj) {
    obj.foo();  //calls Base::foo
}

但是,如果您将上述代码更改为以下代码:

void func(Derived & obj) {
    obj.foo();  //calls Derived:foo
}

现在将选择非常量版本,因为Base::foo隐藏在Derived类中。


由于Derived::foo隐藏了Base::foo,因此不能使用Derived的实例调用后者。

现在,让我们取消隐藏Base::foo并进行更多的实验。

class Derived : public Base {
public:
    using Base::foo;         //<----------------this unhides Base::foo
    virtual void foo() {
        cout << "Derived::foo()" << endl;
    }
};

现在,在Derived中,这两个函数(const和非常量版本)都可用,但未隐藏。现在有几个有趣的问题。

由于现在Derived已取消隐藏这两个函数,下面的每个函数将调用哪个函数?

void f(Derived& obj) {
    obj.foo(); //Which function? Base::foo or Derived::foo?
}
void g(const Derived & obj) {
    obj.foo(); //Which function? Base::foo or Derived::foo?
}

第一个将调用非常量版本的Derived::foo,第二个将调用常量版本的Base::foo。原因很简单,对于const对象,只能调用const函数,但对于非常量对象,两者都可以调用,但如果非常量版本可用,则会选择它。

查看在线演示:http://www.ideone.com/955aY

您没有重写该方法,因为Derived::foo并不完全相同。

要重写方法,基本版本和重写版本必须相同,包括const-ness。

在C++中,可以有两个具有相同名称和参数的函数,其中唯一的区别是const,而不是。

这个想法是,你有时想要不同的行为。例如,一个访问函数可能有不同的返回类型:

class MyClass
{
public:
  virtual Xxx * GetXxx();
  virtual Xxx const * GetXxx() const;
  // ....
}

您可以单独覆盖这些功能。

在您的情况下,当您从非常量对象调用foo时,您调用了函数的非常量变量。由于您已经重写了const变量,基类中的变量就是被调用的变量。

您所做的被称为"重载"。正如@SLaks所指出的,在重写时,签名需要相同。

const是签名的一部分。CCD_ 26是与CCD_ 27不同的函数。你根本没有压倒一切。这就是为什么。