tutorial smart remix programador online español curso c++ oop virtual-functions

c++ - remix - smart contracts ethereum



¿Hay alguna razón para no hacer que una función de miembro sea virtual? (7)

¿Hay alguna razón real para no hacer que una función miembro sea virtual en C ++? Por supuesto, siempre existe el argumento del rendimiento, pero eso no parece mantenerse en la mayoría de las situaciones, ya que la sobrecarga de las funciones virtuales es bastante baja.

Por otro lado, me han picado un par de veces olvidándome de hacer una función virtual que debería ser virtual. Y ese parece ser un argumento más grande que el de rendimiento. Entonces, ¿hay alguna razón para no hacer que las funciones de los miembros sean virtuales por defecto?


Al diseñar su jerarquía de clases, puede tener sentido escribir una función que no deba anularse. Un ejemplo es si está haciendo el patrón de "método de plantilla", donde tiene un método público que llama a varios métodos virtuales privados. No desearía que las clases derivadas lo anularan; todos deberían usar la definición base.

No hay una palabra clave "final", por lo que la mejor forma de comunicar a otros desarrolladores que un método no debe ser anulado es hacerlo no virtual. (que no sean comentarios fácilmente ignorados)

En el nivel de clase, hacer que el destructor no sea virtual comunica que la clase no se debe usar como una clase base, como los contenedores STL.

Hacer que un método no virtual comunique cómo se debe usar.


Hay varias razones.

Primero, el rendimiento: Sí, la sobrecarga de una función virtual es relativamente baja vista aisladamente. Pero también evita que el compilador forme parte de la lista, y esa es una gran fuente de optimización en C ++. La biblioteca estándar de C ++ funciona tan bien como lo hace porque puede alinear las docenas y docenas de líneas pequeñas de las que se compone. Además, una clase con métodos virtuales no es un tipo de datos POD, por lo que se aplican muchas restricciones. No se puede copiar solo por memcpy''ing, se vuelve más costoso de construir y ocupa más espacio. Hay muchas cosas que de repente se vuelven ilegales o menos eficientes una vez que está involucrado un tipo no POD.

Y segundo, buena práctica POO. El punto de una clase es que hace algún tipo de abstracción, oculta sus detalles internos y proporciona una garantía de que "esta clase se comportará así y así, y siempre mantendrá estas invariantes. Nunca terminará en un estado inválido" . Eso es bastante difícil de cumplir si permites que otros anulen cualquier función miembro. Las funciones de miembro que definió en la clase están ahí para garantizar que se mantenga el invariante. Si no nos importa eso, podríamos hacer públicos los miembros internos de los datos y permitir que las personas los manipulen a voluntad. Pero queremos que nuestra clase sea consistente. Y eso significa que tenemos que especificar el comportamiento de su interfaz pública. Esto puede implicar puntos de personalización específicos , al hacer que las funciones individuales sean virtuales, pero casi siempre implica también que la mayoría de los métodos no sean virtuales, de modo que puedan hacer el trabajo de garantizar que se mantenga el invariante. El modismo de interfaz no virtual es un buen ejemplo de esto: http://www.gotw.ca/publications/mill18.htm

En tercer lugar, a menudo no se necesita herencia, especialmente no en C ++. Las plantillas y la programación genérica (polimorfismo estático) en muchos casos pueden funcionar mejor que la herencia (polimorfismo en tiempo de ejecución). Sí, a veces aún necesitas métodos virtuales y herencia, pero ciertamente no es el valor predeterminado. Si es así, lo estás haciendo mal. Trabaja con el lenguaje, en lugar de intentar pretender que era otra cosa. No es Java, y a diferencia de Java, en C ++ la herencia es la excepción, no la regla.


Ignoraré el costo de rendimiento y memoria, porque no tengo manera de medirlos para el caso "en general" ...

Las clases con funciones de miembros virtuales no son POD. Entonces, si desea usar su clase en un código de bajo nivel que dependa de que sea POD, entonces (entre otras restricciones) las funciones de los miembros no deben ser virtuales.

Ejemplos de cosas que puede hacer de forma portátil con una instancia de una clase POD:

  • cópielo con memcpy (siempre que la dirección de destino tenga suficiente alineación).
  • acceder a los campos con offsetof ()
  • en general, trátalo como una secuencia de char
  • ... um
  • Eso es todo. Estoy seguro de que he olvidado algo.

Otras cosas que las personas mencionaron con las que estoy de acuerdo son:

  • Muchas clases no están diseñadas para la herencia. Hacer que sus métodos sean virtuales sería engañoso, ya que implica que las clases secundarias podrían querer anular el método, y no debería haber clases secundarias.

  • Muchos métodos no están diseñados para ser anulados: lo mismo.

Además, incluso cuando las cosas están destinadas a subclasarse / anularse, no están necesariamente destinadas al polimorfismo en tiempo de ejecución. Muy ocasionalmente, a pesar de lo que dice la mejor práctica de OO, para lo que quieres heredar es la reutilización del código. Por ejemplo, si está utilizando CRTP para el enlace dinámico simulado. Entonces, nuevamente, no querrás dar a entender que tu clase jugará muy bien con el polimorfismo en tiempo de ejecución al hacer que sus métodos sean virtuales, cuando nunca deberían llamarse así.

En resumen, las cosas que están destinadas a ser anuladas para el polimorfismo en tiempo de ejecución deben marcarse como virtuales, y las que no, no deberían marcarse. Si encuentra que casi todas sus funciones miembro están destinadas a ser virtuales, márquelas virtuales a menos que haya una razón para no hacerlo. Si encuentra que la mayoría de sus funciones miembro no están destinadas a ser virtuales, entonces no las marque virtuales a menos que haya una razón para hacerlo.

Es un problema complicado cuando se diseña una API pública, porque cambiar un método de uno a otro es un cambio radical, por lo que debe hacerlo bien la primera vez. Pero no necesariamente se sabe antes de tener ningún usuario, si sus usuarios van a querer "polimorfizar" sus clases. Ho hum. El enfoque de contenedor STL, de definición de interfaces abstractas y prohibición de la herencia por completo, es seguro, pero a veces requiere que los usuarios hagan más tipeo.



La siguiente publicación es en su mayoría opinión, pero aquí va:

El diseño orientado a objetos es tres cosas, y la encapsulación (ocultación de información) es la primera de estas cosas. Si un diseño de clase no es sólido en esto, entonces el resto realmente no importa mucho.

Se ha dicho antes que "la herencia rompe la encapsulación" (Alan Snyder ''86). Una buena discusión sobre esto está presente en el grupo de cuatro libros de patrones de diseño. Una clase debe diseñarse para soportar la herencia de una manera muy específica. De lo contrario, abre la posibilidad de uso indebido por parte de los herederos.

Haría la analogía de que hacer que todos tus métodos sean virtuales es similar a hacer públicos a todos tus miembros. Un poco exagerado, lo sé, pero es por eso que utilicé la palabra ''analogía''


Una forma de leer sus preguntas es "¿Por qué C ++ no hace que cada función sea virtual de manera predeterminada, a menos que el programador anule ese valor predeterminado?" Sin consultar mi copia de "Diseño y Evolución de C ++": esto agregaría almacenamiento extra a cada clase a menos que cada función miembro se haga no virtual. Me parece que esto habría requerido más esfuerzo en la implementación del compilador, y ralentizó la adopción de C ++ al proporcionarle forraje a la actuación obsesionada (me cuento en ese grupo).

Otra forma de leer sus preguntas es "¿Por qué los programadores de C ++ no hacen que todas las funciones sean virtuales a menos que tengan muy buenas razones para no hacerlo?" La excusa de rendimiento es probablemente la razón. Dependiendo de su aplicación y dominio, esta podría ser una buena razón o no. Por ejemplo, parte de mi equipo trabaja en plantas de cotización de datos de mercado. Con más de 100.000 mensajes por segundo en una sola transmisión, la sobrecarga de la función virtual sería inaceptable. Otras partes de mi equipo trabajan en una compleja infraestructura comercial. Hacer que la mayoría de las funciones sean virtuales es probablemente una buena idea en ese contexto, ya que la flexibilidad adicional supera a la micro-optimización.


Stroustrup, el diseñador de la lengua, dice :

Porque muchas clases no están diseñadas para usarse como clases base. Por ejemplo, vea el complejo de clases .

Además, los objetos de una clase con una función virtual requieren espacio necesario para el mecanismo de llamada de función virtual, generalmente una palabra por objeto. Esta sobrecarga puede ser significativa y puede obstaculizar la compatibilidad del diseño con los datos de otros idiomas (por ejemplo, C y Fortran).

Consulte El diseño y la evolución de C ++ para obtener más fundamentos de diseño.