我搜索过问题,查看论坛,书籍等。我可以识别方法的多态行为,并且在编译或运行时决定调用的方法时有很多简单的例子。但我对此代码感到困惑,其中C类继承自A:
的B继承class A {
protected:
int x;
public:
virtual void change() = 0;
virtual void change(int a) { x = a; }
};
class B : public A {
public:
void change() { x = 1; }
};
class C : public B {
public:
void change() { x = 2; }
void change(int a) { x = a*2; }
};
int main () {
B *objb = new B();
C *objc = new C();
A *obja;
objb->change();
obja = objc;
objc->change();
obja->change(5);
// ...
}
许多示例告诉(并且很明显)发生了多态行为,并且在运行时中决定执行以下行时要调用的方法:
obja->change(5);
但我的问题是:
当我调用以下内容时会发生什么(从纯虚拟中覆盖)?
objb->change();
当我呼叫以下内容时会发生什么(从虚拟但非纯粹的覆盖)?
objc->change(5);
由于指针变量的类声明与对象相同,是否应在编译或运行时确定方法调用?
答案 0 :(得分:5)
如果编译器可以在编译时推断出实际类型,则可以避免虚函数调度。但它只能在证明行为等同于运行时调度时才能执行此操作。这是否发生取决于您的特定编译器的智能程度。
真正的问题是,你为什么关心?您显然理解调用虚函数的规则,并且行为的语义始终是运行时调度的那些,因此它对您编写代码的方式没有任何影响。
答案 1 :(得分:3)
有三个问题需要考虑。首先是重载决议: 在这种情况下,编译器使用表达式的静态类型 构造它所选择的一组函数。因此,如果你有 写成:
objb->change( 2 );
代码不会编译,因为没有change
int
范围内的B
。如果没有change
的话
在B
的范围内,编译器会进一步查看并找到
change
中的A
(所有这些),但一旦找到名称,就会停止。
这是名称查找和函数重载解析,它完全是 静态的。
第二个问题是编译器应该调用哪个函数 已选择在界面中调用特定功能。如果选择了 函数是虚函数,调用的实际函数将是函数 在动态的最派生类中具有完全相同的签名 type,即所讨论的实际对象的类型。
最后,还有一个问题是动态调度是否被用于 生成的代码。这完全取决于编译器。该 编译器可以做任何想做的事,只要正确的函数,就像 由前两个问题决定,被称为。一般来说:如果 功能不是虚拟的,永远不会使用动态调度;如果 访问是直接对象(命名对象或临时),动态 通常不会使用dispatch,因为编译器可以很简单 知道最衍生的类型。当电话是通过参考或a 指针,编译器一般会使用动态调度,但它是 有时可能编译器跟踪指针足以知道 它将在运行时指向的类型,并放弃动态调度。和 好的编译器通常会使用分析器信息进一步发展 确定99%的时间,将调用相同的函数,并且 调用处于紧密循环中,并将生成两个版本的循环, 一个用动态调度,一个用最频繁调用 函数内联,并通过if,at选择循环的哪个版本 运行时。
答案 2 :(得分:1)
objb->change()
调用B::change()
,因为objb
包含B
类型对象的地址
objc->change(5);
调用C::change(int)
,因为objc
包含C
类型对象的地址
方法调用仍然是 动态/运行时间 ,因为方法B::change()
& C::change(int)
仍然是虚拟的,因为继承了虚拟属性。
回答是否动态调度函数或编译时的问题
答案是否可以肯定地说它是编译时调度还是动态调度。动态/运行时调度首先发生,因为编译器无法确定在编译时调用哪些函数版本,因此如果编译器可以推断出要调用哪个函数的确定方式,则调度可能很好在编译时自己决定。
话虽如此,调度是在运行时还是编译时发生,并不会改变调用哪个版本的重载函数最终被调用的语义,因为C ++标准明确规定了这方面函数函数的规则。 / p>