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

时间:2017-09-05 03:05:19

标签: 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&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();
}                                               

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

2 个答案:

答案 0 :(得分:2)

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

  • 该计划要求延迟装订。
  • 但是早期绑定不会改变程序的可观察行为。早期绑定是可取的,因为它更快,使程序更小,并且需要更少的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()已经由不同的编译器编译。 / 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();
}

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