4

我试图理解以下代码:

#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) 调用 Derived 中的函数 f。我不是 100% 确定为什么。我看了一下这个(http://en.cppreference.com/w/cpp/language/implicit_cast),上面写着:

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

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

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

编辑:这就是我想出来并向自己解释的方式。

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

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

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

4

3 回答 3

11

这两个函数有不同的签名,所以finderived不会覆盖 in 中的虚函数base。仅仅因为类型intfloat可以隐式转换在这里没有效果。

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

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

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

gcc 从中产生错误:

virtual void Derived::f(int)' 标记覆盖,但不覆盖

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

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

编辑:

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

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

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

Base::f(float)
Base::f(float)
于 2012-10-28T11:40:31.017 回答
2

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

  1. 编译器的第一步是选择一个类名。成员函数名称 f 用于执行此操作。不使用参数类型。选择派生。
  2. 编译器的下一步是从该类中选择一个成员函数。使用参数类型。无效派生::f(int); 是 Derived 类中唯一具有正确名称和参数的匹配函数。
  3. 从 float 到 int 的缩小类型转换正在发生。
于 2012-10-28T11:54:42.973 回答
1

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

这意味着当您调用时d->f(3.14f),编译器甚至不会考虑B::f. 它解决了对 的调用D::f。但是,当您调用 时b->f(3.14f),编译器可以选择的唯一版本是B::fasD::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 确实可以转换为整数类型。

于 2012-10-28T11:47:34.947 回答