Pura función virtual con implementación

176

Mi comprensión básica es que no hay implementación para una función virtual pura, sin embargo, me dijeron que podría haber implementación para una función virtual pura.

class A {
public:
    virtual void f() = 0;
};

void A::f() {
    cout<<"Test"<<endl;
}

¿El código de arriba está bien?

¿Cuál es el propósito de convertirlo en una función virtual pura con una implementación?

Skydoor
fuente

Respuestas:

215

Se virtualdebe implementar una función pura en un tipo derivado que se instanciará directamente, sin embargo, el tipo base aún puede definir una implementación. Una clase derivada puede llamar explícitamente a la implementación de la clase base (si los permisos de acceso lo permiten) usando un nombre de ámbito completo (llamando A::f()a su ejemplo, si A::f()fuera publico protected). Algo como:

class B : public A {

    virtual void f() {
        // class B doesn't have anything special to do for f()
        //  so we'll call A's

        // note that A's declaration of f() would have to be public 
        //  or protected to avoid a compile time problem

        A::f();
    }

};

El caso de uso que se me ocurre es cuando hay un comportamiento predeterminado más o menos razonable, pero el diseñador de la clase quiere que ese tipo de comportamiento predeterminado se invoque solo explícitamente. También puede ser el caso de lo que desea que las clases derivadas realicen siempre su propio trabajo, pero también puedan llamar a un conjunto común de funcionalidades.

Tenga en cuenta que, aunque está permitido por el lenguaje, no es algo que veo comúnmente utilizado (y el hecho de que se puede hacer parece sorprender a la mayoría de los programadores de C ++, incluso los experimentados).

Michael Burr
fuente
1
Olvidó agregar por qué sorprende a los programadores: es porque la definición en línea está prohibida por estándar. Definiciones de métodos virtuales puros deben ser deported. (ya sea en un .inl o .cpp para referirse a las prácticas comunes de nomenclatura de archivos).
v.oddou
así que este método de llamada es igual al método de miembro que llama. Algún tipo de método de clase en Java.
Sany Liew
2
"no es de uso común" == ¿mala práctica? Estaba buscando exactamente el mismo comportamiento, tratando de implementar NVI. Y NVI me parece una buena práctica.
Saskia
55
Vale la pena señalar que hacer que A :: f () sea puro significa que B debe implementar f () (de lo contrario, B sería abstracto y no se podría desinstalar). Y como señala @MichaelBurr, proporcionar una implementación para A :: f () significa que B puede usarlo para definir f ().
fearless_fool
2
IIRC, Scot Meyer tiene un excelente artículo sobre el caso de uso de esta pregunta en uno de sus libros clásicos "C ++ más eficaz"
irsis
75

Para ser claros, estás malinterpretando qué = 0; después de una función virtual significa.

= 0 significa que las clases derivadas deben proporcionar una implementación, no que la clase base no pueda proporcionar una implementación.

En la práctica, cuando marca una función virtual como pura (= 0), no tiene mucho sentido proporcionar una definición, porque nunca se llamará a menos que alguien lo haga explícitamente a través de Base :: Función (...) o si el El constructor de la clase base llama a la función virtual en cuestión.

Terry Mahaffey
fuente
9
Esto es incorrecto. Si invoca esa función virtual pura en el constructor de su clase virtual pura, se realizará una llamada virtual pura. En cuyo caso es mejor que tenga una implementación.
rmn
@rmn, sí, tienes razón sobre las llamadas virtuales en constructores. Actualicé la respuesta. Sin embargo, espero que todos sepan no hacer eso. :)
Terry Mahaffey
3
De hecho, hacer una llamada pura base desde un constructor da como resultado un comportamiento definido por la implementación. En VC ++, eso equivale a un bloqueo _purecall.
Ofek Shilon
@OfekShilon es correcto: me sentiría tentado a llamarlo también comportamiento indefinido y candidato a malas prácticas / refactorización de código (es decir, llamar a métodos virtuales dentro del constructor). Supongo que tiene que ver con la coherencia de la tabla virtual, que podría no estar preparada para enrutar al cuerpo de la implementación correcta.
Teodron
1
En constructores y destructores, las funciones virtuales no son virtuales.
Jesper Juhl el
20

La ventaja de esto es que obliga a los tipos derivados a anular el método pero también proporciona una implementación por defecto o aditiva.

JaredPar
fuente
1
¿Por qué querría forzar si hay una implementación predeterminada? Eso suena como funciones virtuales normales. Si solo se trata de una función virtual normal, puedo anular y, si no lo hice, se proporcionará una implementación predeterminada (implementación de la base).
StackExchange123
19

Si tiene un código que debería ser ejecutado por la clase derivada, pero no desea que se ejecute directamente, y desea forzar su anulación.

Su código es correcto, aunque en general esta no es una característica de uso frecuente, y generalmente solo se ve cuando se trata de definir un destructor virtual puro; en ese caso, debe proporcionar una implementación. Lo curioso es que una vez que deriva de esa clase no necesita anular el destructor.

Por lo tanto, el uso razonable de funciones virtuales puras es especificar un destructor virtual puro como una palabra clave "no final".

El siguiente código es sorprendentemente correcto:

class Base {
public:
  virtual ~Base() = 0;
};

Base::~Base() {}

class Derived : public Base {};

int main() { 
  // Base b; -- compile error
  Derived d; 
}
Kornel Kisielewicz
fuente
1
Los destructores de clase base siempre se llaman de todos modos, virtuales o no y puros o no; con otras funciones, no puede garantizar que una función virtual sobresaliente llame a la implementación de la clase base ya sea que la versión de la clase base sea pura o no.
CB Bailey
1
Ese código está mal. Debe definir el dtor fuera de la definición de clase debido a una peculiaridad de sintaxis del lenguaje.
@Roger: gracias, eso realmente me ayudó: este es el código que he estado usando, se compila bien en MSVC, pero supongo que no sería portátil.
Kornel Kisielewicz
4

Si eso es correcto. En su ejemplo, las clases que derivan de A heredan tanto la interfaz f () como una implementación predeterminada. Pero obliga a las clases derivadas a implementar el método f () (incluso si es solo para llamar a la implementación predeterminada proporcionada por A).

Scott Meyers analiza esto en Effective C ++ (2nd Edition) Item # 36 Diferenciar entre herencia de interfaz y herencia de implementación. El número de artículo puede haber cambiado en la última edición.

Yukiko
fuente
4

Las funciones virtuales puras con o sin cuerpo simplemente significan que los tipos derivados deben proporcionar su propia implementación.

Los cuerpos de funciones virtuales puros en la clase base son útiles si sus clases derivadas quieren llamar a su implementación de clase base.

Brian R. Bondy
fuente
2

El 'virtual void foo () = 0;' la sintaxis no significa que no pueda implementar foo () en la clase actual, sí puede. Tampoco significa que deba implementarlo en clases derivadas . Antes de abofetearme, observemos el problema del diamante: (Código implícito, eso sí).

class A
{
public: 
    virtual void foo()=0;
    virtual void bar();
}

class B : public virtual A
{
public:
    void foo() { bar(); }
}

class C : public virtual A
{
public:
    void bar();
}

class D : public B, public C
{}

int main(int argc, const char* argv[])
{
    A* obj = new D();
    **obj->foo();**
    return 0;
}

Ahora, la invocación obj-> foo () dará como resultado B :: foo () y luego C :: bar ().

Verá ... los métodos virtuales puros no tienen que implementarse en clases derivadas (foo () no tiene implementación en la clase C - el compilador compilará) En C ++ hay muchas lagunas.

Espero poder ayudar :-)

Nir Hedvat
fuente
55
No es necesario que se implemente en TODAS las clases derivadas, pero DEBE tener una implementación en todas las clases derivadas que pretenda instanciar. No puede crear una instancia de un objeto de tipo Cen su ejemplo. Puede crear una instancia de un objeto de tipo Dporque se pone la aplicación de foopartir B.
YoungJohn
0

Un caso de uso importante de tener un método virtual puro con un cuerpo de implementación , es cuando quieres tener una clase abstracta, pero no tienes ningún método adecuado en la clase para que sea puramente virtual. En este caso, puede hacer que el destructor de la clase sea virtual puro y poner su implementación deseada (incluso un cuerpo vacío) para eso. Como ejemplo:

class Foo
{
   virtual ~Foo() = 0;
   void bar1() {}
   void bar2(int x) {}
   // other methods
};

Foo::~Foo()
{
}

Esta técnica hace que la Fooclase sea abstracta y, como resultado, imposible crear una instancia de la clase directamente. Al mismo tiempo, no ha agregado un método virtual puro adicional para hacer que la Fooclase sea abstracta.

Gupta
fuente