Search code examples
c++gccvirtual-functionsdynamic-bindingstatic-binding

What should I do to see static and dynamic binding in action? [C++]


I am using GCC on Linux.

I wish to understand working Virtual functions.

What kind of C++ code should I write to see and understand how exactly static and dynamic binding happens with and without Virtual functions?

And how to "see" how they were finally bound and what exactly happened during the process?


Solution

  • Here's an example. You can build and run this code with the function set to be a virtual function or not. To get virtual behavior, dynamic dispatch, dynamic binding, build it with the preprocessor macro IS_VIRTUAL defined. To see static binding build it without defining that macro.

    #include <iostream>
    
    #if defined(IS_VIRTUAL)
    #define CONDITIONAL_VIRTUAL virtual
    #else
    #define CONDITIONAL_VIRTUAL
    #endif
    
    struct A {
      CONDITIONAL_VIRTUAL void foo() { std::cout << "A\n"; }
    };
    
    struct B : A {
      CONDITIONAL_VIRTUAL void foo() { std::cout << "B\n"; }
    };
    
    // global objects
    A a; B b;
    
    enum object_type { get_A, get_B };
    A *get_object(object_type t) {
      switch (t) {
        case get_A: return &a;
        case get_B: return &b;
      }
    }
    
    int main() {
      std::cout << "Choose A or B: ";
      char c;
      std::cin >> c;
    
      A *x = get_object( c == 'A' ? get_A : get_B );
      x->foo();
    }
    

    The binding is relevant to the evaluation of x->foo(). The compiler has to figure out what code to execute for that expression. With both static and dynamic binding, the compiler looks at x and sees its type is A*, so it looks at the struct A and looks for a foo() declaration.

    With static binding the compiler finds that foo() is not virtual, so the compiler just goes ahead and generates code calling that foo() method. Simple.

    With dynamic binding, the compiler sees that method marked as virtual, and so the compiler instead generates code which will, at runtime, use a table of function pointers associated with the object x to select the method to call, and then call whatever method is found. The compiler also generates code elsewhere to create tables for the global a and b objects. For the global a object it makes the table point to A::foo(), and for the global b it makes the table point to B::foo(). So if x points to b object then the table look-up will result in B::foo() and that's the function that will get called.

    In general the compiler has to make sure that all objects which have virtual methods also have a table with them that points to the right functions to call, so that any time a virtual call is made on an object the program can, at runtime, get the table associated with the object and look up the the right method to call.

    So build the above program in both static and dynamic modes, and then run it and observe the output you get for each input. Fill in the table below with the output you get for each combination of input and type of binding.

           Binding |   static     dynamic
    Input
    -----
    A                    ?           ?
    B                    ?           ?
    

    In all cases the output is produced by the evaluation of the same x->foo() method call. In which cases is dynamic binding in evidence? Does this correspond with your understanding of the above explanation of dynamic binding?