Como dice Anders , se trata en parte del rendimiento y en parte del bloqueo de diseños mal pensados para reducir el alcance de los problemas causados posteriormente por personas que heredan ciegamente cosas que no fueron diseñadas para ser heredadas.
El rendimiento parece obvio: si bien la mayoría de los métodos no se notarán en el hardware moderno, un captador virtual podría causar un golpe bastante notable, incluso en el hardware moderno que se basa cada vez más en instrucciones fácilmente predichas en la tubería de la CPU.
Ahora, la razón para hacer que todo sea virtual de manera predeterminada parece estar impulsada por una sola cosa: los marcos de prueba de unidad. Me parece que esta es una razón pobre, en la que estamos cambiando el código para adaptarlo al marco en lugar de diseñarlo correctamente.
Quizás el problema sea con los marcos utilizados aquí, deberían mejorar para permitir el reemplazo de funciones en tiempo de ejecución con cuñas inyectadas en lugar de crear código que lo haga (con todos los trucos para obtener métodos privados y no virtuales, no mencionar funciones estáticas y de terceros)
Entonces, la razón por la cual los métodos no son virtuales por defecto es como dijo Anders, y cualquier deseo de hacerlos virtuales para que se adapten a alguna herramienta de prueba de unidad es poner el carro delante del caballo, el diseño adecuado supera las limitaciones artificiales.
Ahora puede mitigar todo esto mediante el uso de interfaces o reelaborando toda su base de código para usar componentes (o microservicios) que se comunican mediante el paso de mensajes en lugar de llamadas a métodos directamente cableados. Si amplía la superficie de una unidad para probar que es un componente, y ese componente es completamente autónomo, realmente no necesita atornillarlo para probarlo.