对虚函数和派生类的混淆
Confusion over virtual functions and derived classes
我正在尝试理解以下代码:
#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,也就是所谓的函数。
这两个函数具有不同的签名,因此f
inderived
不会覆盖base
中的虚函数。仅仅因为可以隐式强制转换int
和float
类型在这里不起作用。
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); 从示例中:
- 编译器的第一步是选择类名。成员函数名称 f 用于执行此操作。不使用任何参数类型。选择派生。
- 编译器的下一步是从该类中选择一个成员函数。 使用参数类型。 void 派生::f(int); 是派生类中唯一具有正确名称和参数的匹配函数。
- 从浮点型到 int 的缩小类型转换正在发生。
由于这两个函数的参数类型不同,Derived
类中的参数实际上并没有覆盖Base
中的参数类型。相反,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 确实可以转换为整数类型。
- 在派生函数中指定void*参数
- 使用 std::variant<...时调用 BaseState 函数而不是派生函数>
- 从基类数组调用派生函数
- 我设计了一个类并创建了基指针,但是当我尝试通过基指针访问派生函数时,它会出错
- c++ 在派生函数中启动 OMP 线程
- 派生函数的映射
- NIST SP 800-56A 串联/单步密钥派生函数的现有实现
- C++从基类实例调用派生函数
- c++多态性-使用基指针访问派生函数
- 派生函数中的NULL实现
- 与派生函数参数绑定
- 从基类派生函数
- 继承和使用派生函数
- 在列表对象中使用对象的派生函数
- 当派生函数定义了析构函数时,使用复制函数而不是移动函数
- 使用基类的派生函数
- 为什么调用基类函数而不是派生函数
- 多重继承派生类:如何在不重复调用 base 的情况下重用派生函数
- 试图与 Base 中的派生函数交朋友 - 为什么它不起作用?
- 在继续派生函数之前先执行基函数