What exactly are conditions for dynamic binding?

2.1k views Asked by At
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();
}

In first and second case as far as I can tell compiler should already know what types are, so I guess there be no dynamic binding.

But I'm not sure about the third and fourth cases. In third case, in my opinion compiler can know the types if it tries a little hard to guess where the pointer is pointing at. So there may or may not be dynamic binding.

In forth case, would a derived class pointer to derived class object still have to involve dynamic binding?

3

There are 3 answers

0
cdhowie On BEST ANSWER

In general, C++ compilers are allowed to optimize aggressively so long as they follow the as-if rule. That is, they are allowed to optimize in any way as long as the program behaves as-if no optimization happened at all1 -- in terms of observable and defined behavior. (If you invoke undefined behavior then optimizations could well change behavior, but since the behavior wasn't defined to begin with, it's not an issue.)

Anyway, to get back on track, this means that a compiler may indeed compile all of the method calls in your example using static (compile-time) binding because it can prove the actual types of the objects the pointers point to, and using static binding instead of dynamic binding would not cause a change in the observable behavior of the program.

To specifically address your fourth question:

... would a derived class pointer to derived class object still have to involve dynamic binding?

If we assume that the compiler doesn't know the type of the object being pointed to then yes, it must still involve dynamic binding if you are invoking g() on a Bar *. This is because it could point to an object of a class that further derives the Bar type -- maybe you introduce a class Baz : Bar in another compilation unit, or maybe even some third-party library loaded into your program introduces it! The compiler would have no way of knowing, so it must assume that this could happen and so it would use dynamic binding.

However, if the compiler is able to prove that Bar::g() can't be overridden further, either because it is final2 or because the Bar class is final then it could use (and probably would use) static binding when invoking g() on a Bar *.


1 The standard does have specific exceptions to this rule. In particular, the compiler is allowed to elide copies of objects in some cases, which omits calls to copy constructors that would have otherwise been called. If those constructors had observable side-effects then this optimization would change the observable behavior of the program. However, the standard explicitly permits this because copies can be very expensive (think of a vector with a million elements) and because copy constructors shouldn't have side-effects anyway.

2 final is a new keyword in C++11 that prevents a class from being inherited or prevents a virtual function from being overridden.

0
ravi On

Whether static binding or dynamic binding should take place is determined by whether the function is virtual OR non-virtual. Pointers and references are used to get desired run time behavior.

In case of plain objects there's always static binding.

So, in this first two cases would just be the result of static binding. Effect of virtual is lost when you are dealing with plain objects.

And in cases 3 & 4, compiler deduce the static type from the pointer but it also sees the function as virtual so it delay binding to run-time when actual object is known...

0
defube On

Static binding of a virtual function call requires proving the type of object for which the call is being made. If the type cannot be proven, the binding must be dynamic.

The types in case 1 & 2 can be proven, so the binding can be static.

This is also possible in 3 & 4, though this requires proving that a and b did not change since they were assigned.

Things would get interesting if, say, we passed a or b by reference into an externally-defined function. At that point, we'd need information from the translator and linker to determine if the pointers could have changed. Chances are, most compilers would give up here and dynamically bind.