动态绑定的条件究竟是什么?

时间:2014-11-13 04:15:44

标签: c++ oop polymorphism dynamic-binding

class Foo
{
public:
    void f() {}

    virtual void g() {}
};

class Bar : public Foo
{
public:

    void f() {}

    virtual void g() {}
};

int main()
{
    Foo foo;
    Bar bar;

    Foo *a = &bar;
    Bar *b = &bar;

    // case #1
    foo.f();
    foo.g();

    // case #2
    bar.f();
    bar.g();

    // case #3
    a->f();
    a->g();

    // case #4
    b->f();
    b->g();
}

在第一种情况和第二种情况下,据我所知编译器应该已经知道什么类型,所以我猜没有动态绑定。

但我不确定第三和第四个案例。在第三种情况下,在我看来,编译器可以知道类型,如果它尝试一点点难以猜测指针指向的位置。因此可能存在或不存在动态绑定。

在第四种情况下,派生类对象的派生类指针是否仍然必须涉及动态绑定?

3 个答案:

答案 0 :(得分:2)

通常,只要C ++编译器遵循 as-if 规则,就可以进行积极优化。也就是说,只要程序行为 as 在所有 1 上没有进行优化,就可以以任何方式进行优化 - 就可观察和而言定义行为。 (如果调用未定义的行为,那么优化很可能会改变行为,但由于行为没有定义,所以它不是问题。)

无论如何,为了回到正轨,这意味着编译器可能确实使用静态(编译时)绑定编译示例中的所有方法调用,因为它可以证明指针指向的对象的实际类型,并且使用静态绑定而不是动态绑定不会导致程序的可观察行为发生变化。

专门解决您的第四个问题:

  

...派生类对象的派生类指针仍然需要涉及动态绑定吗?

如果我们假设编译器没有知道所指向的对象的类型然后是,那么如果你在一个上调用g(),它仍然必须涉及动态绑定。 Bar *。这是因为它可以指向一个类的对象进一步派生Bar类型 - 也许您在另一个编译单元中引入class Baz : Bar,或者甚至可能在您的程序中加载了一些第三方库介绍它!编译器无法知道,所以它必须假设可以发生,因此它将使用动态绑定。

但是,如果编译器能够证明Bar::g()不能被进一步覆盖,或者因为它是final 2 或者因为{{1} class Bar然后它可以在final上调用g()时使用(也可能会使用)静态绑定。


1 该标准确实有此规则的特定例外。特别是,在某些情况下,允许编译器忽略对象的副本,这省略了对本来会被调用的复制构造函数的调用。如果这些构造函数具有可观察到的副作用,那么这种优化将改变程序的可观察行为。但是,标准明确允许这样做,因为副本可能非常昂贵(考虑具有一百万个元素的向量),并且因为复制构造函数不应该无论如何都有副作用。

2 Bar *是C ++ 11中的一个新关键字,它可以防止继承类或阻止覆盖虚函数。

答案 1 :(得分:1)

是否应该进行静态绑定或动态绑定取决于该函数是虚拟还是非虚拟。指针和引用用于获得所需的运行时行为。

如果是普通对象,则始终存在静态绑定。

因此,前两种情况只是静态绑定的结果。处理普通对象时,虚拟效果会丢失。

在案例3& 4,编译器从指针推导出静态类型,但它也将函数视为虚函数,因此当实际对象已知时,它会延迟绑定到运行时...

答案 2 :(得分:1)

虚拟函数调用的静态绑定需要证明正在进行调用的对象类型。如果无法证明类型,则绑定必须是动态的。

案例1和案例中的类型2可以证明,因此绑定可以是静态的。

这也可以在3& 4,虽然这需要证明ab自分配以来没有变化。

如果我们通过引用将ab传递给外部定义的函数,事情会变得有趣。此时,我们需要来自翻译器链接器的信息来确定指针是否可以更改。很可能,大多数编译器会放弃并动态绑定。