虚拟函数的奇怪行为
Strange behavior with virtual functions
使用以下代码,我希望输出是B.f B.f DD.f,但我得到的输出是B.f B.f B.f。这怎么可能,当DD
从f
虚拟的D
派生时。
class B
{
public:
void f() { cout << "B.f "; }
};
class D : public B
{
public:
virtual void f() { cout << "D.f "; }
};
class DD : public D{
public:
virtual void f() { cout << "DD.f "; }
};
B * b = new B();
B * d = new D();
B * dd = new DD();
b->f();
d->f();
dd->f();
函数从声明virtual
的级别变得virtual
。您首先在 D
中声明f
virtual
,这意味着动态调度只会从D
向上发生。 B
不知道,也不应该知道派生类。
想想编译器是如何看待它的:
您有一个指向B
- B
具有以下定义的指针:
class B
{
public:
void f() { cout << "B.f "; }
};
由于f
不是virtual
,我将继续静态解决调用 - 即 B::f()
.
为了使动态调度从指针到B
工作,您需要在B
中使f()
虚拟:
class B
{
public:
virtual void f() { cout << "B.f "; }
};
将B::f()
设置为virtual,如果没有将B::f()
设置为virtual,它不会出现在B虚拟表(vtbl)中,因此B::f()
被调用而不是调度对派生类的调用。
class B
{
public:
virtual void f() { cout << "B.f "; }
};
一旦你在 B 中声明 f() virtual,它就会开始维护一个虚拟表,该表保存派生类同名的所有其他函数的函数指针。这是一个查找表,用于以动态/后期绑定方式解析函数调用。
当您使用引用或指针调用方法时,编译器会在方法的声明处搜索指针或引用的类型(此处它搜索B
带有签名f()
的某个方法的声明)。当它找到一个:
- 如果它没有标记为
virtual
,那么它会将其解析为对为此类定义的方法的调用 - 这是一个静态绑定。 - 如果它被标记为
virtual
,则调用的方法将是引用或指向的对象中的适当方法 - 这是一个动态绑定。
下一个测试将是:
DD * dd = new DD();
D * d = dd;
B * b = d;
b->f();
d->f();
dd->f();
一个new DD()
使用/查看不同的对象...每种类型都可以被认为是您对对象的一种视图。如果你把它看作是一个B
那么f()
做某事,但总是同样的事情,但如果你把它看作是D
或DD
,f()
做了一些不同的事情......
如果你在街上遇到某人,他向你致敬的标准方式是打招呼,但对于同一个人,当他遇到他的朋友时,他可以打招呼!或者哟!
class Person {
public:
void salute() { cout << "Hello" << endl; }
};
class Friend : public Person {
public:
virtual void salute() { cout << "Hi!" << endl; }
};
class RoomMate : public Friend {
public:
virtual void salute() { cout << "Yo!" << endl; }
};
void asACustomer(Person &p) {
p.salute(); // static binding, we need the standard politeness
}
void asAFriend(Friend &f) {
p.salute(); // dynamic binding, we want an appropriate message...
}
RoomMate joe;
asCustomer(joe);
asFriend(joe);
使用静态绑定,您在编译时知道调用了哪个方法;使用动态绑定则不能,您只知道会有一个合适的方法。这是子类型多态性的关键点。
通常,在混合使用方法的静态和动态绑定时要小心。
- C++无法定义虚拟函数 OUTER 类和头文件
- 用常见虚拟函数实现的任意组合来实现派生类的正确方法是什么
- 尝试将unique_ptrs推送到向量时使用纯虚拟函数错误
- 有没有比在库中添加一个并非由所有派生类实现的新虚拟函数更好的设计实践
- 重载 -> shared_ptr 个实例中的箭头运算符<interface>,接口中没有纯虚拟析构函数
- 当覆盖存在时调用基本虚拟"binded to object"函数
- 如何在C++中伪造虚拟可变参数函数模板?
- 类型擦除的std::function与虚拟函数调用的开销
- 重写虚拟函数和继承
- 是否可以使用函数指针调用虚拟析构函数?
- 在没有动态内存的世界中,我是否需要虚拟析构函数?
- 虚拟继承基构造函数消除
- "虚拟""覆盖"析构函数
- 类中的虚拟布尔函数参数不起作用
- 用纯虚拟函数兜圈子
- 将C++子类成员函数(虚拟实现)传递给 C 类型函数指针
- 尝试在 QLabel 上绘画失败(无法在没有对象的情况下调用成员函数"虚拟无效 QLabel::p aintEvent(QPaintEvent*)")
- 声明析构函数虚拟就足够了吗?
- 视觉 C++当我们在基类中使函数成为纯虚拟时,那么在子类中再次使相同的函数虚拟的必要性是什么
- 重载函数(虚拟/非虚拟)