Просто сделай:
Enemy* n = new Ninja();
Enemy* m = new Monster();
n->sayHere();
n->attack();
m->sayHere();
m->attack();
delete n;
delete m;
Это должно делать, как вы хотите. Вам нужно использовать указатели, чтобы он работал. Причина заключается в том, как работает динамическое связывание.
Всякий раз, когда в программе объявлена виртуальная функция C ++, для класса создается v-таблица. V-таблица состоит из адресов виртуальных функций для классов и указателей на функции из каждого из объектов производного класса. Всякий раз, когда происходит вызов функции для виртуальной функции c ++, v-таблица используется для преобразования в адрес функции. Вот как происходит динамическое связывание во время вызова виртуальной функции.
Идея состоит в том, что компилятор сохраняет указатели на каждый метод на основе адреса памяти объекта. Требуется указатель для доступа к таблице и вызова соответствующего указателя функции. Подумайте, если бы вы хотели написать OO-версию C, как бы вы предоставили такой механизм, как наследование и полиморфизм? Это имеет смысл, когда вы думаете об этом таким образом.
Я только что прочитал, что вы уходите с JAVA. В JAVA большинство ваших объектов хранятся в куче. Это все просто подразумевается.
JAVA в
Enemy n = new Ninja();
n.attack();
примерно эквивалентно
Enemy* n = new Ninja();
n->attack();
Где. Оператор в JAVA больше похож на оператор -> в C ++. В обоих случаях n находится в куче. Java просто скрывает от вас все средства управления указателями и памятью. Вот почему у вас может быть блаженное незнание того, как динамическое связывание работает в JAVA и C #.