对虚函数和派生类的混淆

时间:2012-10-28 11:36:15

标签: c++ virtual-functions name-hiding

我想了解以下代码:

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

  

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

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

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

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

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

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

现在,在上面的代码片段中,我们没有任何重写,因为B :: f和D :: f的签名是不同的(一个是浮点数,另一个是int)。因此,当我们调用b-&gt; f(3.14F)时,唯一考虑的函数是原始的B :: f,这就是所谓的。

3 个答案:

答案 0 :(得分:11)

这两个函数具有不同的签名,因此f中的derived不会覆盖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从中产生错误:

  

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

  

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

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

编辑:

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

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

现在将一个float传递给成员函数f更接近于在base中定义的函数并产生:

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

答案 1 :(得分:2)

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

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

答案 2 :(得分:1)

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

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