Search code examples
c++dynamic-caststatic-cast

using dynamic_cast for runtime type identification


when reading Essential c++ chapter 5.10 Run-time Type identification, I've encountered a problem. Let me introduce a little background first. There are a base class named num_sequence and a class Fibonacci derive from num_sequence. In the base class, there is a virtual function named gen_elems and the derived class has its own definition.

The following comes from the book.

Fibonacci fib;
num_sequence *ps = &fib;
ps->gen_elems(64);

We know that the Fibonacci instance of gen_elems() will be invoked. However, although we know from this test that ps addresses a Fibonacci class object, an attempt to invoke the Fibonacci instance of gen_elems() directly through ps results in a compile-time error:

ps->Fibonacci::gen_elems(64); //gives compile-time error

ps does not know the type of the object it addresses, even if we and the typeid and virtual function mechanisms do.

To invoke the Fibonacci instance of gen_elems(), we must instruct the compiler to convert ps into a pointer of type Fibonacci. The static_cast and dynamic_cast can both do this job.

I'm confused by the bold sentence. ps->gen_elems(64) actually invokes the Fibonacci instance of gen_elems(). Why need we use static_cast and dynamic_cast to convert it to a pointer of type Fibonacci?


Solution

  • Under most situations, you would just call the virtual method normally and let polymorphism do its work to call the most-derived implementation as needed. What the author is trying to explain is that it is also possible to directly call a specific implementation of a virtual method without going through polymorphism.

    Say a class derives from Fibonacci to override gen_elems() again, but you do not want to call that override, you want to call the Fibonacci override. By converting a num_sequence pointer into a Fibonacci pointer (or descendant pointer) at compile-time, it allows the compiler to access the Fiboncci vtable so it can emit code to directly call Fibonacci::gen_elems() (if the object being pointed at during run-time is not actually an instance of Fibanocci or descendant, you will likely crash/corrupt your app. That cannot be validated at compile-time).

    For example:

    class num_sequence
    {
    public:
        virtual void gen_elems(int value)
        {
            std::cout << "num_sequence" << std::endl;
        }
    };
    
    class Fibonacci : public num_sequence
    {
    public:
      void gen_elems(int value)
      {
            std::cout << "Fibonacci" << std::endl;
      }
    };
    
    class SomethingElse : public Fibonacci
    {
    public:
      void gen_elems(int value)
      {
            std::cout << "SomethingElse" << std::endl;
      }
    };
    

    .

    num_sequence ns;
    Fibonacci fib;
    SomethingElse se;
    num_sequence *ps;
    
    ps = &ns;
    ps->gen_elems(64); // displays "num_sequence"
    
    ps = &fib;
    ps->gen_elems(64); // displays "Fibonacci"
    
    ps = &se;
    ps->gen_elems(64); // displays "SomethingElse"
    
    ps->Fiboacci::gen_elems(64); // compiler error!
    
    static_cast<Fibonacci*>(ps)->Fibonacci::gen_elems(64); // displays "Fibonacci"
    static_cast<SomethingElse*>(ps)->Fibonacci::gen_elems(64); // displays "Fibonacci"
    
    Fibonacci *pfib = dynamic_cast<Fibonacci*>(ps);
    if (pfib != NULL)
    {
        pfib->gen_elems(64); // displays "SomethingElse"
        pfib->Fibonacci::gen_elems(64); // displays "Fibonacci"
    }