在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&amp;然后Subtract函数将调用。那么为什么它被称为动态绑定的例子,即使编译器知道在编译时调用哪个函数呢?
我的问题也与虚拟功能有关。考虑遵循前,
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();
}
这里正在发生动态绑定。在运行时决定调用哪个函数。那么为什么编译器无法在编译时决定只调用哪个函数?
答案 0 :(得分:2)
在一般情况下,使用函数指针请求后期绑定,但是足够聪明的编译器可以优化该请求并提前绑定,如果它可以证明只能绑定一个函数。 as-if优化规则允许这样做:
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
);
现在您可以传递Add
或Subtract
作为第四个参数。被调用的函数可能不是同一个二进制文件的一部分(也许它是动态链接库的一部分),因此后期绑定是不可避免的 - accumulate()
已经由不同的编译器编译。 / p>
答案 1 :(得分:0)
如果编译系统可以证明只有一个函数可以调用,则允许(但不是必需)删除间接调用并直接调用该函数。这适用于函数指针和虚方法调度。通常对于虚方法,优化将作为链接时间优化的一部分发生,其中链接器意识到只有一个具有虚方法的给定类的实例,或者可能没有覆盖虚方法的类。
我不确定优化是否被认为是非常重要的,但我希望两者都能在最简单的情况下使用最好的编译器。 (第一个例子将不再采用相当标准的编译器优化技术。对于vtable来说,几乎可以肯定需要一个专用的优化传递,因为编译器必须知道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();
}
编译器无法再在编译时确定调用第二个调度的虚拟方法。