动态绑定的条件到底是什么


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();
}

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

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

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

通常,只要C++编译器遵循 as-if 规则,就可以进行积极优化。 也就是说,只要程序的行为表现得好像根本没有发生优化1 - 就可观察和定义的行为而言,他们就可以以任何方式进行优化。 (如果您调用未定义的行为,那么优化很可能会改变行为,但由于行为一开始就没有定义,所以这不是问题。

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

具体回答你的第四个问题:

。指向派生类对象的派生类指针是否仍必须涉及动态绑定?

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

但是,如果编译器能够证明Bar::g()不能被进一步覆盖,要么因为它是 final 2 或因为 Bar 类是final,那么它可以在Bar *上调用g()时使用(并且可能会使用)静态绑定。


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

2 final 是 C++11 中的一个新关键字,用于防止继承类或阻止覆盖虚函数。

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

对于普通对象,始终存在静态绑定。

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

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

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

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

在3和4中也是可能的,尽管这需要证明ab自分配以来没有改变。

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

相关内容

  • 没有找到相关文章

最新更新