使用非虚拟基础 class 函数与派生 class 未实现的虚拟函数之间的区别
Difference between using non-virtual base class functions versus derived class non-implemented virtual functions
这个问题和What are the differences between overriding virtual functions and hiding non-virtual functions?有点关系,但我不是问技术细节,而是问非虚函数和虚函数的用法。
这里有一些背景知识。假设我有一个基础 class A 和两个派生的 classes B 和 C
#include <iostream>
class A {
public:
A() {};
virtual void foo() { std::cout << "foo() called in A\n"; };
virtual void bar() { std::cout << "bar() called from A\n"; };
void xorp() { std::cout << "xorp() called from A\n"; };
virtual ~A() {};
};
class B : public A {
public:
B() {};
virtual ~B() {};
virtual void foo() override { std::cout << "foo() called in B\n"; };
//virtual void bar() override not implemented in B, using A::bar();
};
class C : public A {
public:
C() {};
virtual ~C() {};
virtual void foo() override { std::cout << "foo() called in C\n"; };
//virtual bar() override not implemented in C, using A::bar();
};
int main() {
A a{};
B b{};
C c{};
a.foo(); //calls A::foo()
a.bar(); //calls A::bar()
a.xorp(); //calls non-virtual A::xorp()
b.foo(); //calls virtual overridden function B::foo()
b.bar(); //calls virtual non-overridden function A::bar()
b.xorp(); //calls non-virtual A::xorp()
c.foo(); //calls virtual overridden function C::foo()
c.bar(); //calls virtual non-overridden function A::bar()
c.xorp(); //calls non-virtual A::xorp()
return 0;
}
如预期的那样输出如下:
foo() called in A
bar() called from A
xorp() called from A
foo() called in B
bar() called from A
xorp() called from A
foo() called in C
bar() called from A
xorp() called from A
如果我在派生的 classes 中保留虚函数 bar() 未实现,则在派生的 classes B 和 C 中对 bar() 的任何调用都会解析为 A::bar()。 xorp() 是一个非虚函数,也可以从派生的 classes 调用为 b.xorp() 或 b.A::xorp()。
例如,如果我要在 B 中实现 xorp(),它会有效地隐藏 A::xorp() 并且对 b.xorp() 的调用实际上是对 b.B::xorp().
这让我想到了我的问题,使用上面的例子。假设我有一个辅助函数,派生的 classes 需要它来实现它们。
辅助函数是非虚拟成员函数(如 xorp())与辅助函数是派生的虚拟函数之间有区别吗 classes不要覆盖 (bar())?
通读有关 class 对象布局和 VTABLE 的演示文稿(https://www.cs.bgu.ac.il/~asharf/SPL/Inheritance.pptx,幻灯片 28-35)我无法真正发现差异,因为非虚函数和非重写虚函数指向同一个地方(即基础函数class)
谁能给我一个例子,说明这两种方法会产生不同的结果,或者是否有我没有发现的警告?
您的示例中的缺陷是您没有使用多态性。您直接对所有对象进行操作。您不会注意到任何与覆盖相关的事情,因为 none 的调用 需要 动态解析。而如果调用不是动态解析的话,虚函数和非虚函数是绝对没有区别的。要查看差异,请使用辅助免费功能:
void baz(A& a) {
a.foo();
a.bar();
a.xorp();
}
int main() {
// As before
baz(a);
baz(b);
baz(c);
}
现在您应该能够看到对 foo
、bar
和 baz
的调用的解析方式存在明显差异。特别是...
If I were to implement a xorp() in B, for example, it would effectively hide the A::xorp() and a call to b.xorp() would actually be a call to b.B::xorp().
...在 baz
.
中将不再为真
Is there a difference between having the helper function be a non-virtual member function (like xorp()), versus the helper function being a virtual function that the derived classes do not override (bar())?
如果您将一个方法标记为虚拟但从未重写它,那么它的行为将等同于您从未将其标记为虚拟。与它在对象中调用的其他方法的关系不受影响。
这并不是说还没有"differences"。
它无疑向阅读代码的人传达了不同的意图。如果您的 xorp() 方法不是虚拟的——而是依赖于虚拟方法来实现它的行为——那么人们就会将 "xorpiness" 理解为具有某些固定属性。他们可能会尝试避免在任何派生的 classes 中重新定义 xorp(),并且知道只能通过定义它所依赖的虚拟方法来间接影响 xorpiness。
此外,编译器无法始终知道您是否要使用虚拟覆盖。因此它无法优化虚拟分派的额外代码——即使您不使用它 "taking advantage"。 (有时它可以,比如如果你有一个 class 你永远不会从它派生也不会导出它......虚拟可能会被丢弃。)
对于导出的 classes,您希望其他人使用:仅仅因为 您 从未覆盖方法并不意味着其他人不会。除非你使用 final
并防止派生你的对象 (这被认为不是非常友好,除非你有充分的理由)。所以如果你做一些虚拟的东西,能力就在那里,一旦别人添加了一个覆盖然后是的——那时候会有不同。
这个问题和What are the differences between overriding virtual functions and hiding non-virtual functions?有点关系,但我不是问技术细节,而是问非虚函数和虚函数的用法。
这里有一些背景知识。假设我有一个基础 class A 和两个派生的 classes B 和 C
#include <iostream>
class A {
public:
A() {};
virtual void foo() { std::cout << "foo() called in A\n"; };
virtual void bar() { std::cout << "bar() called from A\n"; };
void xorp() { std::cout << "xorp() called from A\n"; };
virtual ~A() {};
};
class B : public A {
public:
B() {};
virtual ~B() {};
virtual void foo() override { std::cout << "foo() called in B\n"; };
//virtual void bar() override not implemented in B, using A::bar();
};
class C : public A {
public:
C() {};
virtual ~C() {};
virtual void foo() override { std::cout << "foo() called in C\n"; };
//virtual bar() override not implemented in C, using A::bar();
};
int main() {
A a{};
B b{};
C c{};
a.foo(); //calls A::foo()
a.bar(); //calls A::bar()
a.xorp(); //calls non-virtual A::xorp()
b.foo(); //calls virtual overridden function B::foo()
b.bar(); //calls virtual non-overridden function A::bar()
b.xorp(); //calls non-virtual A::xorp()
c.foo(); //calls virtual overridden function C::foo()
c.bar(); //calls virtual non-overridden function A::bar()
c.xorp(); //calls non-virtual A::xorp()
return 0;
}
如预期的那样输出如下:
foo() called in A
bar() called from A
xorp() called from A
foo() called in B
bar() called from A
xorp() called from A
foo() called in C
bar() called from A
xorp() called from A
如果我在派生的 classes 中保留虚函数 bar() 未实现,则在派生的 classes B 和 C 中对 bar() 的任何调用都会解析为 A::bar()。 xorp() 是一个非虚函数,也可以从派生的 classes 调用为 b.xorp() 或 b.A::xorp()。
例如,如果我要在 B 中实现 xorp(),它会有效地隐藏 A::xorp() 并且对 b.xorp() 的调用实际上是对 b.B::xorp().
这让我想到了我的问题,使用上面的例子。假设我有一个辅助函数,派生的 classes 需要它来实现它们。
辅助函数是非虚拟成员函数(如 xorp())与辅助函数是派生的虚拟函数之间有区别吗 classes不要覆盖 (bar())?
通读有关 class 对象布局和 VTABLE 的演示文稿(https://www.cs.bgu.ac.il/~asharf/SPL/Inheritance.pptx,幻灯片 28-35)我无法真正发现差异,因为非虚函数和非重写虚函数指向同一个地方(即基础函数class)
谁能给我一个例子,说明这两种方法会产生不同的结果,或者是否有我没有发现的警告?
您的示例中的缺陷是您没有使用多态性。您直接对所有对象进行操作。您不会注意到任何与覆盖相关的事情,因为 none 的调用 需要 动态解析。而如果调用不是动态解析的话,虚函数和非虚函数是绝对没有区别的。要查看差异,请使用辅助免费功能:
void baz(A& a) {
a.foo();
a.bar();
a.xorp();
}
int main() {
// As before
baz(a);
baz(b);
baz(c);
}
现在您应该能够看到对 foo
、bar
和 baz
的调用的解析方式存在明显差异。特别是...
If I were to implement a xorp() in B, for example, it would effectively hide the A::xorp() and a call to b.xorp() would actually be a call to b.B::xorp().
...在 baz
.
Is there a difference between having the helper function be a non-virtual member function (like xorp()), versus the helper function being a virtual function that the derived classes do not override (bar())?
如果您将一个方法标记为虚拟但从未重写它,那么它的行为将等同于您从未将其标记为虚拟。与它在对象中调用的其他方法的关系不受影响。
这并不是说还没有"differences"。
它无疑向阅读代码的人传达了不同的意图。如果您的 xorp() 方法不是虚拟的——而是依赖于虚拟方法来实现它的行为——那么人们就会将 "xorpiness" 理解为具有某些固定属性。他们可能会尝试避免在任何派生的 classes 中重新定义 xorp(),并且知道只能通过定义它所依赖的虚拟方法来间接影响 xorpiness。
此外,编译器无法始终知道您是否要使用虚拟覆盖。因此它无法优化虚拟分派的额外代码——即使您不使用它 "taking advantage"。 (有时它可以,比如如果你有一个 class 你永远不会从它派生也不会导出它......虚拟可能会被丢弃。)
对于导出的 classes,您希望其他人使用:仅仅因为 您 从未覆盖方法并不意味着其他人不会。除非你使用 final
并防止派生你的对象 (这被认为不是非常友好,除非你有充分的理由)。所以如果你做一些虚拟的东西,能力就在那里,一旦别人添加了一个覆盖然后是的——那时候会有不同。