在虚方法中调用虚方法时,是否应该发生虚分派
Should virtual dispatch happen when a virtual method is called within a virtual method using object?
struct B
{
virtual void bar () {}
virtual void foo () { bar(); }
};
struct D : B
{
virtual void bar () {}
virtual void foo () {}
};
现在我们用B
的对象称foo()
为
B obj;
obj.foo(); // calls B::bar()
:应该通过virtual
调度来解析bar()
,还是使用对象的静态类型(即B
)来解析。
我想我误解了你的问题。我很确定这取决于编译器的优化器有多聪明。一个朴素的实现当然仍然会通过虚拟查找。对于一个特定的实现,唯一确定的方法是编译代码并查看反汇编,看看它是否足够聪明,可以直接调用。
原始回答:
它将被虚拟地调度。当你考虑到在一个类方法中,一个方法调用是像this->bar();
这样的东西时,这一点就更明显了,这使得很明显,一个指针被用来调用方法,允许使用动态对象类型。
然而,在你的例子中,因为你创建了一个B
,它当然会调用B
的方法版本。
请注意(如注释所示),即使使用隐式this->
,虚拟分派也不会在构造函数内部发生。
EDIT2为您更新:这完全不对。B::foo()
中的调用通常不能静态地绑定(除非由于内联编译器知道对象的静态类型)。仅仅因为它知道它是在B*
上被调用的,并不能说明所讨论对象的实际类型——它可能是D*
,并且需要虚拟调度。
必须是虚呼叫。您正在编译的代码无法知道是否存在一个派生程度更高的类,该类实际上已经覆盖了另一个函数。
注意,这假定您正在分别编译它们。如果编译器内联了对foo()的调用(因为它的静态类型是已知的),它也会内联对bar()的调用。
答案:从语言的角度来看,B::foo()
内部对bar()
的调用是通过虚拟调度来解决的。
some_object_ptr->SomeClass::some_method();
some_object.SomeClass::some_method();
在这种情况下,忽略左边对象的动态类型,直接调用特定的方法。
就语言而言,在所有其他情况下都发生了虚拟分派。也就是说,根据对象的动态类型解析调用。换句话说,从形式化的角度来看,每次通过直接对象调用虚方法时,如B obj;
obj.foo();
方法通过"虚拟调度"机制调用,而不考虑上下文("是否在虚拟方法中"—无关紧要)。
在c++语言中就是这样。其他一切都只是编译器所做的优化。您可能知道,当通过直接对象执行调用时,大多数(如果不是全部)编译器将生成对虚方法的非虚调用。当然,这是一个明显的优化,因为编译器知道对象的静态类型与其动态类型相同。同样,它不依赖于上下文("在虚拟方法内"与否-无关紧要)。
在虚方法内部,可以在不指定左侧对象的情况下进行调用(就像在您的示例中一样),这实际上意味着所有调用的左侧都隐式地存在this->
。同样的规则也适用于这种情况。如果您只调用bar()
,它代表this->bar()
,并且调用是虚拟调度的。如果调用B::bar()
,它代表this->B::bar()
,并且调用是非虚拟的。其他的一切都只取决于编译器的优化能力。
你想说的"因为,一旦你在B::foo()
内部,可以肯定这是B*
类型而不是D*
"对我来说是完全不清楚的。这种说法没有抓住要点。虚拟调度取决于对象的动态类型。注意:这取决于*this
的类型,而不是this
的类型。this
是什么类型并不重要。重要的是*this
的动态类型。当您在B::foo
内部时,*this
的动态类型仍然完全有可能是D
或其他类型。因此,必须动态解析对bar()
的调用。
完全取决于实现。名义上它是一个虚调用,但您不能假设所发出的代码实际上会通过虚函数表或类似的方法执行间接调用。
如果在任意的B*
上调用foo()
,那么为foo()
发出的代码当然需要对bar()
进行虚调用,因为referand可能属于派生类。
这不是一个任意的B*
,这是一个动态类型的B
对象。虚函数或非虚函数调用的结果是完全相同的,所以编译器可以随心所欲("as-if"规则),而一个符合规则的程序无法分辨出两者的区别。
特别是在这种情况下,如果对foo
的调用是内联的,那么我认为优化器有充分的机会在它内部对bar
的调用进行非虚拟化,因为它确切地知道obj
的虚表(或等效)中有什么。如果调用不是内联的,那么它将使用foo()
的"香草"代码,这当然需要做一些间接操作,因为它与在任意B*
上进行调用时使用的代码相同。
在本例中:
B obj;
obj.foo(); // calls B::bar()
编译器可以优化掉虚拟分派,因为它知道实际对象的类型是B
。
B::foo()
内部,对bar()
的调用通常需要使用虚拟调度(尽管编译器可能能够内联调用,并且对于,特定的调用实例可能会再次优化虚拟调度)。具体来说,你提出的这句话:
无论
foo()
是使用对象还是指针/引用调用的,任何虚拟B::foo()
内部的所有调用都应该静态解析。因为,一旦你在B::foo()里面,它肯定是B*
类型而不是D*
是不正确的
考虑:
struct D2 : B
{
// D2 does not override bar()
virtual void foo () {
cout << "hello from D2::bar()" << endl;
}
};
现在如果你在某个地方有以下内容:
D2 test;
B& bref = test;
bref.foo();
对foo()
的调用将在B::foo()
结束,但当B::foo()
调用bar()
时,它需要调度到D2::bar()
。
实际上,现在我已经把它打出来了,B&
在这个例子中是完全不必要的。
- 为不同配置设置MSVC_RUNTIME_LIBRARY的正确方法是什么
- 通过方法访问结构
- 最小硬币更换问题(自上而下方法)
- C++为构建时间获取QDateTime的可靠方法
- 在C#中处理C++指针而不使用unsafe的最佳方法
- 处理多个异常集合的C++方法
- 如果C++类在类方法中具有动态分配,但没有构造函数/析构函数或任何非静态成员,那么它仍然是POD类型吗
- 有什么方法可以遍历结构吗
- 当类在C++中定义时,有什么方法可以"register"类吗?
- 在C++中,将大的无符号浮点数四舍五入为整数的最佳方法是什么
- 实现无开销push_back的最佳方法是什么
- 使用std::函数映射对象方法
- 有符号的int和int-有没有一种方法可以在C++中区分它们
- C++从另一个类访问公共静态向量的正确方法是什么
- C++优先级队列,按对象的唯一指针的特定方法升序排列
- 没有为自己的结构调用列表推回方法
- 有没有什么方法可以使用一个函数中定义的常量变量,也可以由c++中同一程序中的其他函数使用
- 在类定义之后定义一个私有方法
- 枚举环境变量的惯用C++14/C++17方法
- 在虚方法中调用虚方法时,是否应该发生虚分派