Polymorphism in Java

There are many pitfalls when approaching polymorphism in C++ the Java way. By default the JRE knows the real underlying type and uses its methods (Think like a compiler, act like a runtime):

public class Animal {
    public void talk() {
        System.out.println("I'm an animal.");
    }
}

public class Tiger extends Animal{
    @Override
    public void talk() {
        System.out.println("I'm a tiger.");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal a = new Animal();
        a.talk();

        Tiger t = new Tiger();
        t.talk();

        Animal tigerInDisguise = new Tiger();
        tigerInDisguise.talk();
    }
}

I'm an animal.
I'm a tiger.
I'm a tiger.

The output would be the same without @Override

Polymorphism in C++

Hiding a non-virtual function

#include <iostream>

class Animal {
public:
    void talk() {
        std::cout << "I'm an animal." << std::endl;
    }
};

class Tiger : public Animal {
public:
    void talk() {
        std::cout << "I'm a tiger." << std::endl;
    }

};

int main() {
    Animal a;
    a.talk();

    Tiger t;
    t.talk();

    return 0;
}

I'm an animal.
I'm a tiger.

This works because the inheriting tiger class hides or overshadows the method of the parent class and is usually not what we want. The next example shows the problem:

The virtual keyword

int main() {
    Animal a;
    a.talk();

    Tiger t;
    t.talk();

    Animal* secretTiger = new Tiger;
    secretTiger->talk();

    return 0;
}
I'm an animal.
I'm a tiger.
I'm an animal.

When the member function in the base class is not marked as virtual, polymorphism doesn’t work as expected.

The correct version looks like this:

#include <iostream>

class Animal {
public:
    // adding virtual keyword
    virtual void talk() {
        std::cout << "I'm an animal." << std::endl;
    }
};

class Tiger : public Animal {
public:
    // adding override improve readability and get compiler help
    void talk() override {
        std::cout << "I'm a tiger." << std::endl;
    }

};

int main() {
    Animal a;
    a.talk();

    Tiger t;
    t.talk();

    Animal* secretTiger = new Tiger();
    secretTiger->talk();

    return 0;
}
I'm an animal.
I'm a tiger.
I'm a tiger.

Pitfall object slicing

Polymorphism in C++ needs to be implemented using pointers as in the example above. The naive approach by just using the assignment operator leads to object slicing, where everything that is not in the base class, gets cut off.

int main() {
    // wrong:
    Animal slicedTiger = Tiger();
    slicedTiger.talk();
    return 0;
}
I'm an animal.

Full example

By using pointers for polymorphism, objects (rather their pointers) can be collected in a common container regardless wether their objects live on the heap or stack:

#include <iostream>
#include <array>

class Animal {
public:
    virtual void talk() {
        std::cout << "I'm an animal." << std::endl;
    }
};

class Tiger : public Animal {
public:
    void talk() override {
        std::cout << "I'm a tiger." << std::endl;
    }

};

int main() {
    Animal a;
    a.talk();

    Tiger t;
    t.talk();

    Animal slicedTiger = Tiger();
    slicedTiger.talk();

    std::cout << "\nWho is in the animal bus?" << std::endl;
    Animal* a_ptr = &a;
    Animal* t_ptr = &t;
    Animal* secretTiger = new Tiger();
    std::array<Animal*, 3> animalBus {a_ptr, t_ptr, secretTiger};

    for (Animal* ptr: animalBus) {
        ptr->talk();
    }

    return 0;
}
I'm an animal.
I'm a tiger.
I'm an animal.

Who is in the animal bus?
I'm an animal.
I'm a tiger.
I'm a tiger.