2010-03-11 58 views
5

Creo que la implementación de la función virtual se habla mucho. Mi pregunta es ¿qué hay de la función virtual pura? Sin embargo, se implementa? En la tabla virtual, ¿cómo decirlo es puro o no puro? ¿Cuál es la diferencia entre la función virtual pura y la función virtual con la implementación?Cómo implementar la función virtual pura en C++

+1

http://stackoverflow.com/questions/2156634/why-pure-virtual-function-is-initialized-by-0 –

Respuesta

4

Normalmente no hay diferencia de implementación entre las funciones virtuales puras y no puras. Siempre que se defina una función puramente virtual, actúa como cualquier otra función virtual. Si no está definido, solo causa un problema si se llama explícitamente.

Existen dos grandes diferencias en el comportamiento, pero generalmente no hay impacto en la implementación del mecanismo de función virtual en sí. El compilador no debe permitirle construir un objeto de un tipo que tenga funciones virtuales puras que no tengan una anulación final no pura en su jerarquía de herencia y cualquier intento de realizar una llamada virtual a una función virtual pura, directa o indirectamente desde el constructor o destructor de un objeto causa un comportamiento indefinido.

+0

Es un comportamiento indefinido si se llama virtualmente. Si la función tiene una implementación, se puede llamar con el formato 'AbstractBase :: Foo()'. –

+0

Si no está definido, ¿tiende a obtener un error de enlazador si intenta llamarlo explícitamente? - Pero, ¿cómo se puede llamar de manera virtual, si no se puede tener una instancia de tipo abstracto? – UncleBens

+0

@UncleBens: No sé, solo estaba parafraseando parte de 10.4 del Estándar. –

0

No sé la implementación real, pero una buena opción sería implementarla como un puntero NULL en el vtable. En otras palabras, si tiene una implementación, hay un puntero de función válido en vtable y si es puramente virtual, tiene un puntero NULL.

Esto es tan lógico que incluso creo que es implementado de esta manera :-).

+0

pero la función virtual pura todavía puede tener implementación, ¿verdad? – skydoor

+0

Una función virtual pura no puede tener una implementación en la clase que la declara; de lo contrario, por definición, no es una función virtual pura. Tiene que ser implementado en clases derivadas. – codenheim

+2

@skydoor: Sí, puede, pero la implementación tiene que ser llamada específicamente. Aún evitará que la clase en la que está se declare un objeto (y todas las subclases que no anulen la virtual pura). –

3

virtual void foo() = 0;

Una función virtual pura sigue siendo una función virtual, por lo que estaría en el vtable, pero el compilador no requiere una implementación para él, y prohibirá la creación de instancias de la clase base que declara el virtual puro. Como todavía puede desreferenciar los punteros de tipo de la clase base abstracta, la tabla virtual debe tener una entrada para el polimorfismo y el enlace de tiempo de ejecución.

¿Eso ayuda?

+1

Declarar una función virtual pura hace que se agregue una entrada para la función en el vtable. En la clase que declara la función virtual pura (y en las clases derivadas que no la anulan), la mayoría de las implementaciones configuran esta entrada para invocar algún tipo de función de diagnóstico que muestre algún mensaje de error (por ejemplo, "Error: función virtual pura llamada "). Todavía es posible definir una función virtual pura, pero debe invocarse de forma no virtual. –

+0

Oh, tienes razón. Gracias. Una función virtual pura también puede tener una definición en la clase base. +1 para su corrección. – codenheim

2

Esta no es una respuesta, sino más bien una continuación de los comentarios a this answer above

El lenguaje C++ define cómo funciona el mecanismo de despacho virtual se lleva a cabo durante la construcción. Al crear instancias de un objeto en una jerarquía, se llama al constructor base (*). En ese punto, el mecanismo de despacho virtual se inicializa para la clase base. En esta etapa, las funciones virtuales se enviarán a la implementación base. Cualquier llamada a un método virtual no puro que utilice el mecanismo de despacho virtual (sin calificación explícita de clase) llamará a la implementación base.

Después de que se complete el constructor base, el mecanismo de expedición virtual (por lo general vtable) se reseted a la versión tipo derivado, y cualquier llamada dinámica a partir de ahí se llame a la versión derivada de los métodos:

struct base { 
    virtual void non_pure() { std::cout << "base::non_pure" << std::endl; } 
    virtual void pure_not_implemented() = 0; 
    virtual void pure_implemented() = 0; 

    base() {      // at this point the object is a ´base´ 
     non_pure();    // base::non_pure 
     // pure_not_implemented(); // runtime error: pure virtual method called 
     pure_implemented();  // base::pure_implemented 
     // base::pure_not_implemented(); // link error 
    } 
}; 
void base::pure_implemented() { std::cout << "base::pure_implemented" << std::endl; } 
struct derived : base { 
    virtual void non_pure() { std::cout << "derived::non_pure" << std::endl; } 
    virtual void pure_not_implemented() { std::cout << "derived::pure_not_implemented" << std::endl; } 
    virtual void pure_implemented() { std::cout << "derived::pure_implemented" << std::endl; 

    derived() {    // after the implicit call to the base class 
           // this is a ´derived´ object, now calls will 
           // get dispatched to derived:: implementations 
     non_pure();    // derived::non_pure 
     pure_not_implemented(); // derived::pure_not_implemented 
     pure_implemented();  // derived::pure_implemented 
     base::non_pure();  // base::non_pure 
     // base::pure_not_implemented() // link error 
    } 
}; 

Tenga en cuenta que existen diferencias entre utilizar el mecanismo de despacho dinámico (generalmente, vtable) y llamar a un método específico con el nombre completo. Se permitirá una llamada a un método virtual puro no implementado a través de la calificación completa en tiempo de compilación, pero fallará en el momento del enlace (el vinculador no puede encontrar una implementación para llamar).

Esta es una decisión importante en el diseño del idioma. La otra opción (la que tomó Java) es inicializar el mecanismo de despacho virtual al tipo más derivado desde el principio, antes de llamar al constructor de la clase base.El problema que tiene este enfoque es que si el constructor base llama a un método virtual (que en Java son todos) se enviará a la implementación más derivada y se ejecutará en un objeto aún no construido, posiblemente causando resultados inesperados:

public class Base 
{ 
    public Base() { 
     f(); 
    } 
    public void f() { 
     System.out.println("Base.f"); 
    } 
} 
public class Derived extends Base { 
    public final int constant; 
    public Derived() { constant = 5; } 
    public void f() { 
     System.out.println("Derived.f() " + constant); 
    } 
    public static void main(String args[]) { 
     Derived d = new Derived(); // prints Derived.f() 0 
    } 
} 

En la versión de Java, el mecanismo de envío dinámico considera que el objeto es del tipo Derived desde el principio. La llamada al f() en el constructor base se enviará dinámicamente a la implementación derivada. En el ejemplo anterior, incluso si la variable se declara como final y por lo tanto una constante por el valor de 5 (parece obvio en el código), el valor impreso real es 0 porque el constructor de Derived no se ha ejecutado.

(*) Esto es demasiado simplista, pero los detalles en realidad no afectan el argumento.

+0

Realmente me preguntaba por qué llamar a un virtual puro desde el constructor sería un comportamiento indefinido, pero llamar a uno no puro no lo sería. Específicamente, ¿sería tan difícil de detectar para los compiladores? Si no se desea realizar una llamada a una función pura (por el motivo que sea), ¿por qué no necesita el diagnóstico del compilador? – UncleBens

+0

Llamar a una llamada de método virtual no pura tendrá el efecto de llamar a la implementación más derivada e incluir el tipo actual, pero no de sus descendientes. El efecto de llamar a un método virtual puro no está definido ya que podría depender del mecanismo de envío. La mayoría de los compiladores llamará al código sintetizado para diagnosticar y matar la aplicación con un mensaje sensato. –

+0

En el punto donde el compilador está procesando el constructor puede saber si es un método virtual puro implementado (si puede ver la definición), pero no puede garantizar que el método virtual puro no se implemente en una unidad de traducción diferente (tal vez su constructor está subrayado en la declaración de clase y se está incluyendo desde una unidad de traducción, mientras que el método virtual puro se define en una unidad de traducción diferente) –

Cuestiones relacionadas