Método virtual privado en C ++

125

¿Cuál es la ventaja de hacer que un método privado sea virtual en C ++?

He notado esto en un proyecto de código abierto C ++:

class HTMLDocument : public Document, public CachedResourceClient {
private:
    virtual bool childAllowed(Node*);
    virtual PassRefPtr<Element> createElement(const AtomicString& tagName, ExceptionCode&);
};
Silverburgh
fuente
9
Creo que la pregunta es al revés. La razón para hacer algo virtual es siempre la misma: permitir que las clases derivadas lo anulen. Entonces la pregunta debería ser: ¿cuál es la ventaja de hacer que un método virtual sea privado? A lo que la respuesta es: hacer que todo sea privado por defecto. :-)
ShreevatsaR
1
@ShreevatsaR Pero ni siquiera respondiste tu propia pregunta ......
Spencer
@ShreevatsaR Pensé que te referías al revés de una manera diferente: ¿Cuál es la ventaja de hacer que un método virtual no sea privado?
Peter - Restablece a Monica el

Respuestas:

115

Herb Sutter lo ha explicado muy bien aquí .

Pauta # 2: Prefiere hacer privadas las funciones virtuales

Esto permite que las clases derivadas anulen la función para personalizar el comportamiento según sea necesario, sin exponer aún más las funciones virtuales directamente al hacerlas invocables por clases derivadas (como sería posible si las funciones solo estuvieran protegidas). El punto es que existen funciones virtuales para permitir la personalización; a menos que también necesiten ser invocados directamente desde el código de las clases derivadas, no hay necesidad de hacerlos nunca más que privados

Prasoon Saurav
fuente
Como puede suponer por mi respuesta, creo que la directriz n. ° 3 de Sutter empuja la directriz n. ° 2 por la ventana.
Spencer el
66

Si el método es virtual, puede ser anulado por clases derivadas, incluso si es privado. Cuando se llama al método virtual, se invocará la versión anulada.

(En oposición a Herb Sutter citado por Prasoon Saurav en su respuesta, C ++ FAQ Lite recomienda contra virtuales privados , principalmente porque a menudo confunde a las personas).

algo
fuente
41
Parece ser que el C ++ FAQ Lite desde entonces ha cambiado su recomendación: " C ++ FAQ anteriormente recomienda el uso de los virtuales protegidas en lugar de los virtuales privadas Sin embargo, el enfoque privada virtual es bastante común ahora que la confusión de los novatos es una preocupación menor.. "
Zack El Humanos el
19
La confusión de los expertos, sin embargo, sigue siendo motivo de preocupación. Ninguno de los cuatro profesionales de C ++ sentados a mi lado conocía los virtuales privados.
Newtonx
12

A pesar de todas las llamadas para declarar privado a un miembro virtual, el argumento simplemente no aguanta. Con frecuencia, la anulación de una función virtual de una clase derivada tendrá que llamar a la versión de la clase base. No puede si se declara private:

class Base
{
 private:

 int m_data;

 virtual void cleanup() { /*do something*/ }

 protected:
 Base(int idata): m_data (idata) {}

 public:

 int data() const { return m_data; }
 void set_data (int ndata) { m_data = ndata; cleanup(); }
};

class Derived: public Base
{
 private:
 void cleanup() override
 {
  // do other stuff
  Base::cleanup(); // nope, can't do it
 }
 public:
 Derived (int idata): base(idata) {}
};

Usted tiene que declarar el método de la clase base protected.

Luego, debe tomar el desagradable recurso de indicar mediante un comentario que el método debe ser anulado pero no llamado.

class Base
{
 ...
 protected:
 // chained virtual function!
 // call in your derived version but nowhere else.
 // Use set_data instead
 virtual void cleanup() { /* do something */ }
 ...

Así, la guía # 3 de Herb Sutter ... Pero el caballo está fuera del granero de todos modos.

Cuando declaras algo protected, confías implícitamente en el escritor de cualquier clase derivada para que comprenda y use adecuadamente las partes internas protegidas, tal como una frienddeclaración implica una confianza más profunda para los privatemiembros.

Los usuarios que tienen un mal comportamiento al violar esa confianza (por ejemplo, etiquetados como 'despistados' al no molestarse en leer su documentación) solo tienen la culpa.

Actualización : He recibido algunos comentarios que afirman que puede "encadenar" implementaciones de funciones virtuales de esta manera utilizando funciones virtuales privadas. Si es así, me gustaría verlo.

Los compiladores de C ++ que uso definitivamente no permitirán que una implementación de clase derivada llame a una implementación de clase base privada.

Si el comité de C ++ se relajara "privado" para permitir este acceso específico, estaría todo para funciones virtuales privadas. Tal como están las cosas, todavía se nos aconseja cerrar la puerta del establo después de que el caballo sea robado.

Spencer
fuente
3
Encuentro su argumento inválido. Usted, como desarrollador de una API, debe luchar por una interfaz que sea difícil de usar incorrectamente y no configurar a otro desarrollador por sus propios errores al hacerlo. Lo que desea hacer en su ejemplo podría implementarse solo con métodos virtuales privados.
sigy
1
Eso no es lo que estaba diciendo. Pero puede reestructurar su código para lograr el mismo efecto sin la necesidad de llamar a una función de clase base privada
sigy
3
En su ejemplo, desea ampliar el comportamiento de set_data. Instrucciones m_data = ndata;y, por cleanup();lo tanto, podrían considerarse invariantes para todas las implementaciones. Por lo tanto, haga que cleanup()no sea virtual y privado. Agregue una llamada a otro método privado que sea virtual y el punto de extensión de su clase. Ahora ya no es necesario que sus clases derivadas llamen a la base cleanup(), su código se mantiene limpio y su interfaz es difícil de usar incorrectamente.
sigy
2
@sigy Eso solo mueve los postes. Necesitas mirar más allá del ejemplo criminal. Cuando hay más descendientes que necesitan llamar a todos los cleanup()s de la cadena, el argumento se desmorona. ¿O está recomendando una función virtual adicional para cada descendiente en la cadena? Ick Incluso Herb Sutter permitió funciones virtuales protegidas como un vacío en su directriz # 3. De todos modos, sin un código real nunca me convencerás.
Spencer
2
Entonces, aceptemos estar en desacuerdo;)
sigy
9

Me encontré con este concepto por primera vez mientras leía '' C ++ eficaz '' de Scott Meyers, Artículo 35: Considere alternativas a las funciones virtuales. Quería hacer referencia a Scott Mayers para otros que puedan estar interesados.

Es parte del patrón de método de plantilla a través del idioma de interfaz no virtual : los métodos de cara al público no son virtuales; más bien, envuelven las llamadas a métodos virtuales que son privadas. La clase base puede ejecutar la lógica antes y después de la llamada a la función virtual privada:

public:
  void NonVirtualCalc(...)
  {
    // Setup
    PrivateVirtualCalcCall(...);
    // Clean up
  }

Creo que este es un patrón de diseño muy interesante y estoy seguro de que puede ver cómo es útil el control agregado.

  • ¿Por qué hacer la función virtual private? La mejor razón es que ya hemos proporcionado un publicmétodo de enfrentamiento.
  • ¿Por qué no simplemente hacerlo protectedpara que pueda usar el método para otras cosas interesantes? Supongo que siempre dependerá de su diseño y de cómo cree que encaja la clase base. Yo diría que el creador de la clase derivada debería centrarse en implementar la lógica requerida; todo lo demás ya está a cargo. Además, está la cuestión de la encapsulación.

Desde una perspectiva de C ++, es completamente legítimo anular un método virtual privado a pesar de que no podrá llamarlo desde su clase. Esto es compatible con el diseño descrito anteriormente.

Pooven
fuente
3

Los uso para permitir que las clases derivadas "llenen los espacios en blanco" para una clase base sin exponer ese agujero a los usuarios finales. Por ejemplo, tengo objetos altamente dinámicos que se derivan de una base común, que solo puede implementar 2/3 de la máquina de estado general (las clases derivadas proporcionan el 1/3 restante dependiendo de un argumento de plantilla, y la base no puede ser una plantilla para otras razones).

NECESITO tener la clase base común para que muchas de las API públicas funcionen correctamente (estoy usando plantillas variadas), pero no puedo dejar que ese objeto salga a la naturaleza. Peor aún, si dejo los cráteres en la máquina de estado, en forma de funciones virtuales puras, en cualquier lugar que no sea "Privado", permito que un usuario inteligente o despistado derivado de una de sus clases secundarias anule los métodos que los usuarios nunca deberían tocar. Entonces, puse los 'cerebros' de la máquina de estado en funciones virtuales PRIVADAS. Luego, los hijos inmediatos de la clase base llenan los espacios en blanco en sus anulaciones NO virtuales, y los usuarios pueden usar de forma segura los objetos resultantes o crear sus propias clases derivadas adicionales sin preocuparse por estropear la máquina de estado.

En cuanto al argumento de que no deberías TENER métodos virtuales públicos, digo BS. Los usuarios pueden anular incorrectamente los virtuales privados con la misma facilidad que los públicos: después de todo, están definiendo nuevas clases. Si el público no debe modificar una API determinada, no la haga virtual en absoluto en objetos de acceso público.

Zack Yezek
fuente