对虚函数和派生类的混淆

Confusion over virtual functions and derived classes

本文关键字:派生 函数      更新时间:2023-10-16

我正在尝试理解以下代码:

#include<iostream>
using namespace std;
class Base {
public:
virtual void f(float) { cout << "Base::f(float)n"; }
};
class Derived : public Base {
public:
virtual void f(int) { cout << "Derived::f(int)n"; }
};
int main() {
Derived *d = new Derived();
Base *b = d;
d->f(3.14F);
b->f(3.14F);
}

这打印

Derived::f(int)
Base::f(float)

我不确定为什么。

第一次调用 d->f(3.14F) 调用派生中的函数 f。我不是100%确定为什么。我看了一下这个(http://en.cppreference.com/w/cpp/language/implicit_cast),上面写着:

浮点类型的 prvalue 可以转换为任何整数类型的 prvalue。小数部分被截断,即小数部分被丢弃。如果值无法适应目标类型,则行为未定义

对我来说,你不能这样做,因为浮点数不适合整数。为什么允许这种隐式转换?

其次,即使我只是接受上述内容是可以的,对b->f(3.14F)的第二次调用也没有意义。 b->f(3.14F) 正在调用一个虚函数 f,因此可以动态解析为调用与 b 指向的对象动态类型关联的 f(),这是一个派生对象。由于我们被允许将 3.14F 转换为 int,因为第一个函数调用表明这是合法的,因此(据我所知)应该再次调用 Derived::f(int) 函数。然而,它在基类中调用函数。这是为什么呢?

编辑:这是我如何弄清楚并向自己解释的。

b 是指向 Base对象的指针,因此我们只能使用 b 来访问 Base 对象的成员,即使 b 确实指向某个派生对象(这是标准的 OO/继承内容)。

此规则的唯一例外是将 Base 的成员函数声明为虚拟函数时。在这种情况下,派生对象可能会重写此函数,通过使用完全相同的签名提供另一个实现。如果发生这种情况,那么这个派生实现将在运行时调用,即使我们碰巧通过指向 Base 对象的指针访问成员函数。

现在,在上面的代码片段中,我们没有发生任何覆盖,因为 B::f 和 D::f 的签名不同(一个是浮点数,另一个是 int)。所以当我们调用 b->f(3.14F) 时,唯一考虑的函数是原来的 B::f,也就是所谓的函数。

这两个函数具有不同的签名,因此finderived不会覆盖base中的虚函数。仅仅因为可以隐式强制转换intfloat类型在这里不起作用。

virtual void f(float) { cout << "Base::f(float)n"; }
virtual void f(int) { cout << "Derived::f(int)n"; }

使用 C++11 中的新override关键字可以看到正在发生的事情的线索,这对于减少此类错误非常有效。

virtual void f(int) override { cout << "Derived::f(int)n"; }

GCC 从中产生错误:

虚拟无效 派生::f(int)' 标记覆盖,但不覆盖

错误:"f"标记为"覆盖",但不覆盖任何成员函数

http://en.cppreference.com/w/cpp/language/override

编辑:

对于第二点,您实际上可以从base中公开float重载derived公开隐式兼容的成员函数。 像这样:

class Derived : public Base {
public:
using Base::f;
virtual void f(int) { cout << "Derived::f(int)n"; }
};

现在将浮点传递给成员函数f绑定更接近基中定义的函数并生成:

Base::f(float)
Base::f(float)

考虑隐藏的简单方法如下 - 查看行 d->f(3.14F); 从示例中:

  1. 编译器的第一步是选择类名。成员函数名称 f 用于执行此操作。不使用任何参数类型。选择派生。
  2. 编译器的下一步是从该类中选择一个成员函数。 使用参数类型。 void 派生::f(int); 是派生类中唯一具有正确名称和参数的匹配函数。
  3. 从浮点型到 int 的缩小类型转换正在发生。

由于这两个函数的参数类型不同,Derived类中的参数实际上并没有覆盖Base中的参数类型。相反,Derived::f隐藏Base::f(目前没有我的标准,所以我不能引用这一章)。

这意味着当你调用d->f(3.14f)时,编译器甚至不考虑B::f。它解决了对D::f的调用。但是,当您调用b->f(3.14f)时,编译器可以选择的唯一版本是B::fD::f不会覆盖它。

你对If the value can not fit into the destination type, the behavior is undefined的解读是错误的。它说而不是类型。所以值 3.0f 适合 int,但 3e11 不适合。在后一种情况下,行为是未定义的。报价的第一部分A prvalue of floating-point type can be converted to prvalue of any integer type.解释了为什么d->f(3.14f)解析为D::f(int)- float 确实可以转换为整数类型。