在C++中,为什么编译器不了解基类对象在编译时指向哪个对象?



C++,为什么编译器在编译时不了解基类对象指向哪个对象?

例如。

int Add(int nX, int nY)
{
return nX + nY;
}
int Subtract(int nX, int nY)
{
return nX - nY;
}
int main()
{
// Create a function pointer and make it point to the Add function
int (*pFcn)(int, int) = Add;
cout << pFcn(5, 3) << endl; // add 5 + 3
pFcn = Subtract;
cout<< pFcn(5,3)<<endl // pefrom 5-3
return 0;
}

上面的代码片段是后期绑定或动态绑定的示例。

在上面的例子中,在编译时,只有我们知道第一个Add函数将通过pFcn调用,然后减去函数将调用。那么为什么即使编译器只知道在编译时调用哪个函数,它也被称为动态绑定的示例?


我的问题也是关于虚拟函数的。 考虑以下 ex,

class Base {
public:
void NonVirtual() {
cout << "Base NonVirtual called.n";
}
virtual void Virtual() {
cout << "Base Virtual called.n";
}
};
class Derived : public Base {
public:
void NonVirtual() {
cout << "Derived NonVirtual called.n";
}
void Virtual() {
cout << "Derived Virtual called.n";
}
};
int main() {
Base* bBase = new Base();
Base* bDerived = new Derived();
bBase->NonVirtual();
bBase->Virtual();
bDerived->NonVirtual();
bDerived->Virtual();
}                                               

这里正在发生动态绑定。要调用的函数在运行时决定。那么为什么编译器不能在编译时只决定调用哪个函数呢?

在一般情况下,使用函数指针请求后期绑定,但如果足够智能的编译器可以证明只有一个函数可以被绑定,则可以优化该请求并提前绑定。 这是假设优化规则允许的:

  • 该计划要求后期绑定。
  • 早期绑定不会改变程序的可观察行为。早期绑定是可取的,因为它更快,使程序更小,并且需要更少的RAM(pFcn可以删除)。
  • 因此,可以改为进行早期结合。

相反,请考虑在编译器无法提前绑定的情况下传递函数指针的位置,要么是因为它不够智能,无法检测到它是可能的,要么是因为它正在与它无法观察到的代码交互:

using BinaryIntegerOperator = int (*)(int, int);
int Add(int a, int b) { return a + b; }
int Subtract(int a, int b) { return a - b; }
extern int accumulate(
int initial,
int *first,
std::size_t count,
BinaryIntegerOperator op
);

现在,您可以将AddSubtract作为第四个参数传递。 被调用的函数可能不是同一二进制文件的一部分(也许它是动态链接库的一部分),因此后期绑定是不可避免的——accumulate()已经被不同的编译器编译了。

如果编译系统可以证明只有一个函数可以调用,则允许(但不是必需)删除间接调用并直接调用该函数。这适用于函数指针和虚拟方法调度。通常,对于虚拟方法,优化将作为链接时间优化的一部分进行,其中链接器意识到给定类只有一个具有虚拟方法的实例,或者可能没有类覆盖虚拟方法。

我不确定这两种优化都被认为非常重要,但我希望两者都发生在最好的编译器的简单情况下。(第一个示例将脱离相当标准的编译器优化技术。为 vtables 执行此操作几乎肯定需要一个专门构建的优化传递,因为编译器必须知道 vtable 是如何在运行时

修改的。一般来说,使用一种机制并不仅仅意味着人们不使用命名的机制。这就是为什么有人可能会说函数指针是动态绑定的,即使编译器可以证明它没有有趣的动态行为。

给定上述更新的代码,请考虑以下事项:

int main(int argc, char **argv) {
Base* bBase = new Base();
Base* bDerived = new Derived();
// Use test on argc to abstract arbitrary computation.
Base *one_or_the_other = (argc > 1) ? bDerived : bBase;
one_or_the_other->NonVirtual();
one_or_the_other->Virtual();
}

编译器无法再在编译时确定为第二次调度调用哪个虚拟方法。

最新更新