¿Cuál es el punto de una función virtual pura privada?

139

Encontré el siguiente código en un archivo de encabezado:

class Engine
{
public:
    void SetState( int var, bool val );
    {   SetStateBool( int var, bool val ); }

    void SetState( int var, int val );
    {   SetStateInt( int var, int val ); }
private:
    virtual void SetStateBool(int var, bool val ) = 0;    
    virtual void SetStateInt(int var, int val ) = 0;    
};

Para mí, esto implica que la Engineclase o una clase derivada de ella, tiene que proporcionar la implementación de esas funciones virtuales puras. Pero no pensé que las clases derivadas pudieran tener acceso a esas funciones privadas para volver a implementarlas, entonces, ¿por qué hacerlas virtuales?

BeeBand
fuente

Respuestas:

209

La pregunta en el tema sugiere una confusión bastante común. La confusión es bastante común, que las preguntas frecuentes de C ++ abogaron contra el uso de virtuales privados, durante mucho tiempo, porque la confusión parecía ser algo malo.

Entonces, para deshacerse de la confusión primero: Sí, las funciones virtuales privadas pueden anularse en las clases derivadas. Los métodos de clases derivadas no pueden llamar a funciones virtuales desde la clase base, pero pueden proporcionar su propia implementación para ellas. Según Herb Sutter, tener una interfaz pública no virtual en la clase base y una implementación privada que se puede personalizar en las clases derivadas, permite una mejor "separación de la especificación de la interfaz de la especificación del comportamiento personalizable de la implementación". Puede leer más al respecto en su artículo "Virtualidad" .

Sin embargo, hay una cosa más interesante en el código que presentó, que merece más atención, en mi opinión. La interfaz pública consta de un conjunto de funciones no virtuales sobrecargadas y esas funciones llaman funciones virtuales no públicas, no sobrecargadas. Como es habitual en el mundo de C ++, es un idioma, tiene un nombre y, por supuesto, es útil. El nombre es (¡sorpresa, sorpresa!)

"Públicos no virtuales sobrecargados Llamadas virtuales no sobrecargadas protegidas"

Ayuda a gestionar adecuadamente la regla de ocultación . Puede leer más sobre esto aquí , pero trataré de explicarlo en breve.

Imagine que las funciones virtuales de la Engineclase también son su interfaz y es un conjunto de funciones sobrecargadas que no es puramente virtual. Si fueran puramente virtuales, todavía se podría encontrar el mismo problema, como se describe a continuación, pero más abajo en la jerarquía de clases.

class Engine
{
public:
    virtual void SetState( int var, bool val ) {/*some implementation*/}
    virtual void SetState( int var, int val )  {/*some implementation*/}
};

Ahora supongamos que desea crear una clase derivada y necesita proporcionar una nueva implementación solo para el método, que toma dos entradas como argumentos.

class MyTurbochargedV8 : public Engine
{
public:
    // To prevent SetState( int var, bool val ) from the base class,
    // from being hidden by the new implementation of the other overload (below),
    // you have to put using declaration in the derived class
    using Engine::SetState;

    void SetState( int var, int val )  {/*new implementation*/}
};

Si olvidó poner la declaración de uso en la clase derivada (o redefinir la segunda sobrecarga), podría meterse en problemas en el siguiente escenario.

MyTurbochargedV8* myV8 = new MyTurbochargedV8();
myV8->SetState(5, true);

Si no evitaste la ocultación de los Enginemiembros, la declaración:

myV8->SetState(5, true);

llamaría void SetState( int var, int val )desde la clase derivada, convirtiendo truea int.

Si la interfaz no es virtual y la implementación virtual no es pública, como en su ejemplo, el autor de la clase derivada tiene un problema menos en el que pensar y simplemente puede escribir

class MyTurbochargedV8 : public Engine
{
private:
    void SetStateInt(int var, int val )  {/*new implementation*/}
};
Maciej Hehl
fuente
¿Por qué la función virtual tiene que ser privada? ¿Puede ser público?
Rico
Me pregunto si las pautas dadas por Herb Sutter en su artículo de "Virtualidad" aún se mantienen hoy.
nurabha
@Rich Podría, pero al hacerlos no públicos, puede transmitir su intención con mayor claridad. Primero, muestra una separación de preocupaciones si te limitas a hacer que la interfaz sea pública y la implementación no pública. En segundo lugar, si desea heredar clases para poder llamar a las implementaciones base, puede declararlas protegidas; si solo desea que proporcionen sus propias implementaciones sin llamar a las implementaciones básicas, debe hacerlas privadas.
Dan
43

La función virtual pura privada es la base del idioma de la interfaz no virtual (OK, no siempre es puramente virtual, pero todavía es virtual allí). Por supuesto, esto también se usa para otras cosas, pero creo que esto es muy útil (: en dos palabras: en una función pública, podría poner algunas cosas comunes (como el registro, las estadísticas, etc.) al principio y al final de la función y luego, "en el medio" para llamar a esta función virtual privada, que será diferente para la clase derivada específica.

class Base
{
    // ..
public:
    void f();
private:
    virtual void DerivedClassSpecific() = 0;
   // ..
};
void Base::f()
{
    //.. Do some common stuff
    DerivedClassSpecific();
    //.. Some other common stuff
}
// ..

class Derived: public Base
{
    // ..
private:
    virtual void DerivedClassSpecific();
    //..
};
void Derived::DerivedClassSpecific()
{
    // ..
}

Virtual puro : solo obliga a las clases derivadas a implementarlo.

EDITAR : Más sobre esto: Wikipedia :: NVI-idiom

Kiril Kirov
fuente
17

Bueno, por un lado, esto permitiría que una clase derivada implemente una función que la clase base (que contiene la declaración de función virtual pura) puede llamar.

Michael Goldshteyn
fuente
55
que solo la clase base puede llamar!
underscore_d
4

EDITAR: declaraciones aclaradas sobre la capacidad de anulación y la capacidad de acceder / invocar.

Podrá anular esas funciones privadas. Por ejemplo, el siguiente ejemplo artificial funciona ( EDITAR: hace que el método de clase derivada sea privado y suelte la invocación del método de clase derivada main()para demostrar mejor la intención del patrón de diseño en uso ):

#include <iostream>

class Engine
{
public:
  void SetState( int var, bool val )
  {
    SetStateBool( var, val );
  }

  void SetState( int var, int val )
  {
    SetStateInt( var, val );
  }

private:

    virtual void SetStateBool(int var, bool val ) = 0;
    virtual void SetStateInt(int var, int val ) = 0;

};

class DerivedEngine : public Engine
{
private:
  virtual void SetStateBool(int var, bool val )
  {
    std::cout << "DerivedEngine::SetStateBool() called" << std::endl;
  }

  virtual void SetStateInt(int var, int val )
  {
    std::cout << "DerivedEngine::SetStateInt() called" << std::endl;
  }
};


int main()
{
  DerivedEngine e;
  Engine * be = &e;

  be->SetState(4, true);
  be->SetState(2, 1000);
}

Private virtualLos métodos en una clase base como los de su código generalmente se usan para implementar el patrón de diseño del Método de plantilla . Ese patrón de diseño le permite a uno cambiar el comportamiento de un algoritmo en la clase base sin cambiar el código en la clase base. El código anterior donde se invocan los métodos de clase base a través de un puntero de clase base es un ejemplo simple del patrón de Método de plantilla.

Vacío
fuente
Ya veo, pero si las clases derivadas tienen algún tipo de acceso de todos modos, ¿por qué molestarse en hacerlas privadas?
BeeBand
@BeeBand: el usuario tendría acceso a las anulaciones de método virtual de clase derivada pública, pero no tendría acceso a las de clase base. El autor de la clase derivada en este caso también podría mantener privado el método virtual. De hecho, haré el cambio en el código de muestra anterior para enfatizar eso. De cualquier manera, siempre podrían heredar públicamente y anular los métodos virtuales de clase base privada, pero aún así solo tendrían acceso a sus propios métodos virtuales de clase derivados. Tenga en cuenta que estoy haciendo una distinción entre anulación y acceso / invocación.
No
porque te equivocas la visibilidad de herencia entre clases Enginey DerivedEngineno tiene nada que ver con lo que DerivedEnginepuede o no puede anular (o acceder, para el caso).
wilhelmtell
@wilhelmtell: suspiro Por supuesto, tienes razón. Actualizaré mi respuesta en consecuencia.
Anulado el
3

El método virtual privado se utiliza para limitar el número de clases derivadas que pueden anular la función dada. Las clases derivadas que tienen que anular el método virtual privado deberán ser amigos de la clase base.

Se puede encontrar una breve explicación de DevX.com .


EDITAR Un método virtual privado se utiliza de manera efectiva en el Patrón de método de plantilla . Las clases derivadas pueden anular el método virtual privado, pero las clases derivadas no pueden llamar a su método virtual privado de clase base (en su ejemplo, SetStateBooly SetStateInt). Solo la clase base puede llamar efectivamente a su método virtual privado ( solo si las clases derivadas necesitan invocar la implementación base de una función virtual, hacer que la función virtual esté protegida ).

Se puede encontrar un artículo interesante sobre la virtualidad .

Buhake Sindi
fuente
2
@Gentleman ... hmmm desplácese hacia abajo hasta el comentario de Colin D Bennett. Parece pensar que "una función virtual privada puede ser anulada por clases derivadas, pero solo se puede llamar desde dentro de la clase base". @Michael Goldshteyn también piensa así también.
BeeBand
Supongo que has olvidado el principio de que una clase derivada no puede ver una clase privada. Esas son las reglas de OOP y se aplican a todos los idiomas que son OOP. Para que una clase derivada implemente su método virtual privado de clase base, tiene que ser uno friendde la clase base. Qt adoptó el mismo enfoque cuando implementaron su modelo de documento DOM XML.
Buhake Sindi
@ Gentleman: No, no lo he olvidado. Hice un error tipográfico en mi comentario. En lugar de "acceder a los métodos de la clase base", debería haber escrito "puede anular los métodos de la clase base". La clase derivada ciertamente puede anular el método de la clase base virtual privada, incluso si no puede acceder a ese método de la clase base. El artículo de DevX.com que señaló era incorrecto (herencia pública). Prueba el código en mi respuesta. A pesar del método privado de la clase base virtual, la clase derivada puede anularlo. No confundamos la capacidad de anular un método de clase base virtual privado con la capacidad de invocarlo.
Nulo
@Gentleman: @wilhelmtell señaló el error en mi respuesta / comentario. Mi reclamo sobre la herencia que afectaba la accesibilidad de clase derivada del método de clase base estaba desactivado. He eliminado el comentario ofensivo a tu respuesta.
Nulo
@Void, veo que una clase derivada puede anular su método virtual privado de la clase base pero no puede usarlo. Entonces, esto es esencialmente un patrón de Método de plantilla.
Buhake Sindi
0

TL; respuesta DR:

Puede tratarlo como otro nivel de encapsulación, en algún lugar entre protegido y privado : no puede llamarlo desde la clase secundaria, pero puede anularlo.

Es útil al implementar el patrón de diseño del Método de plantilla . Puede usar protegido , pero privado junto con virtual puede considerarse como una mejor opción, debido a una mejor encapsulación.

jaskmar
fuente