O que acontece é o seguinte. O dynamic_cast foi criado para garantir que a operação de cast é segura.
É bastante fácil identificar se um cast é feito do filho para o pai. Como o pai faz parte da declaração do filho, o compilador sabe que essa é sempre uma situação válida e pode fazer o cast. Agora, a situação inversa não é verdadeira. Considere:
Esse é um cast feito em Runtime. É impossível saber se o objeto retornado é mesmo do tipo Filho ou não (ele poderia ser do tipo Filho2, ou simplesmente do tipo Pai, o que tornaria o cast inviável). O cast, para testar isso, terá que se basear em algum tipo de informação.
É válido lembrar aqui, que o C++ não tem qualquer tipo de informação de reflexão associada a objetos, tal como o Java. Então, nesse caso, ele precisará se basear em algum outro tipo de informação que ele tenha.
A maior parte das implementações usa então o seguinte artifício: Ela vai até a vtable da classe, e testa se essa vtable é compatível com a da classe filha em questão. Se for, então a conversão de tipos será válida. Entretanto, para que esse artifício funcione é necessário existir uma vtable, o que só ocorre com um método virtual (e, no mínimo, um destrutor virtual deve existir, caso contrário, é um erro de programação fazer herança sobre a classe pois você pode criar memory leaks no seu programa).
Note que o comportamento do dynamic cast é dependente de implementação. Um compilador pode, por exemplo, funcionar para qualquer tipo de classe (virtual ou não) caso a opção de RTTI esteja ligada: inclusive a recomendação geral é que ela sempre esteja, se você faz operações desse tipo. Ele então pode se basear no RTTI e não só na vtable para fazer a comparação. Alguns compiladores das antigas usavam comparações ainda mais lentas e sinistras, baseadas em tabelas de strings, para fazer o dynamic_cast funcionar.
Seu código está bastante interessante. Está lendo o Effective C++?