Pregunta

Supongamos que tengo dos clases de C ++:

class A
{
public:
  A() { fn(); }

  virtual void fn() { _n = 1; }
  int getn() { return _n; }

protected:
  int _n;
};

class B : public A
{
public:
  B() : A() {}

  virtual void fn() { _n = 2; }
};

Si escribo el siguiente código:

int main()
{
  B b;
  int n = b.getn();
}

Uno podría esperar que n se establece en 2.

Resulta que n se pone a 1. ¿Por qué?

¿Fue útil?

Solución

Llamar a funciones virtuales a partir de un constructor o destructor es peligroso y debe evitarse siempre que sea posible. Todo implementaciones de C ++ deben llamar a la versión de la función definida a nivel de la jerarquía en el constructor actual y no más allá.

El C ++ FAQ Lite cubre esta en la sección 23,7 en muy buen detalle. Sugiero leer eso (y el resto de la AYUDA) para un seguimiento.

Extracto:

  

[...] En un constructor, el mecanismo de llamada virtual está deshabilitado porque anulando de las clases derivadas aún no ha sucedido. Los objetos se construyen a partir de la base hacia arriba, “base antes de deriva”.

     

[...]

     

Destrucción se hace “clase derivada antes de la clase base”, por lo que las funciones virtuales se comportan como en los constructores: Sólo se utilizan las definiciones locales - y no se hacen llamadas a las funciones primordiales de evitar tocar la (ahora destruido) derivado parte de clase de la objeto.

Editar Se ha corregido más de todo (gracias litb)

Otros consejos

Llamar a una función polimórfica de un constructor es una receta para el desastre en la mayoría de los lenguajes orientados a objetos. Diferentes idiomas llevará a cabo de manera diferente cuando se encuentra esta situación.

El problema básico es que en todos los idiomas del tipo (s) base debe ser construido previo al tipo derivado. Ahora, el problema es ¿qué significa llamar a un método polimórfico desde el constructor. ¿Qué esperas que se comporte como? Hay dos enfoques: llamar al método a nivel de Base (++ estilo C) o llamar al método polimórfico en un objeto sin construir en la parte inferior de la jerarquía (manera Java)

.

En C ++ la clase Base construirá su versión de la tabla de métodos virtuales antes de entrar en su propia construcción. En este punto, una llamada al método virtual va a terminar llamando a la versión base del método o la producción de un método virtual pura llama en caso de que no tiene aplicación en ese nivel de la jerarquía. Después de que la base ha sido totalmente construido, el compilador se iniciará la construcción de la clase derivada, y se anulará los punteros método para apuntar a las implementaciones en el siguiente nivel de la jerarquía.

class Base {
public:
   Base() { f(); }
   virtual void f() { std::cout << "Base" << std::endl; } 
};
class Derived : public Base
{
public:
   Derived() : Base() {}
   virtual void f() { std::cout << "Derived" << std::endl; }
};
int main() {
   Derived d;
}
// outputs: "Base" as the vtable still points to Base::f() when Base::Base() is run

En Java, el compilador va a construir el equivalente tabla virtual en el primer paso de la construcción, antes de entrar en el constructor o constructor Base Derivado. Las implicaciones son diferentes (y para mi gusto más peligroso). Si el constructor de la clase base llama un método que se overriden en la clase derivada de la llamada realmente será manejado a nivel derivada llamar a un método en un objeto sin construir, produciendo resultados inesperados. Todos los atributos de la clase derivada que se inicializan en el interior del bloque constructor son todavía no inicializado, incluidos los atributos 'finales'. Elementos que tienen un valor predeterminado definido en el nivel de clase tendrán ese valor.

public class Base {
   public Base() { polymorphic(); }
   public void polymorphic() { 
      System.out.println( "Base" );
   }
}
public class Derived extends Base
{
   final int x;
   public Derived( int value ) {
      x = value;
      polymorphic();
   }
   public void polymorphic() {
      System.out.println( "Derived: " + x ); 
   }
   public static void main( String args[] ) {
      Derived d = new Derived( 5 );
   }
}
// outputs: Derived 0
//          Derived 5
// ... so much for final attributes never changing :P

Como se ve, llamando a métodos polimórficos ( Virtual en la terminología C ++) es una fuente común de errores. En C ++, por lo menos usted tiene la garantía de que nunca va a llamar a un método en un objeto aún sin construir ...

La razón es que los objetos C ++ se construyen como las cebollas, de adentro hacia afuera. Grandes clases se construyen clases antes de derivados. Por lo tanto, antes de que un B puede hacerse, debe hacerse una A. Cuando el constructor de A se llama, no es un B, sin embargo, por lo que la tabla de funciones virtuales todavía tiene la entrada para la copia de una de fn ().

C ++ FAQ Lite Cubiertas esta bastante bien:

  

Esencialmente, durante la llamada al constructor de clases de base, el objeto no es todavía del tipo derivado y por lo tanto la aplicación de la Tipo de base de la función virtual se llama y no la de tipo derivado.

Una solución a su problema está utilizando métodos de fábrica para crear su objeto.

  • Definir una clase base común para su jerarquía de clases que contiene un afterConstruction método virtual ():
class Object
{
public:
  virtual void afterConstruction() {}
  // ...
};
  • Definir un método de fábrica:
template< class C >
C* factoryNew()
{
  C* pObject = new C();
  pObject->afterConstruction();

  return pObject;
}
  • Utilice esta manera:
class MyClass : public Object 
{
public:
  virtual void afterConstruction()
  {
    // do something.
  }
  // ...
};

MyClass* pMyObject = factoryNew();

¿Sabe usted la caída de error del explorador de Windows ?! "llamada de función virtual pura ..."
El mismo problema ...

class AbstractClass 
{
public:
    AbstractClass( ){
        //if you call pureVitualFunction I will crash...
    }
    virtual void pureVitualFunction() = 0;
};

Debido a que no hay Implementación para la pureVitualFunction () y la función se llama en el constructor del programa se bloqueará.

Los vtables son creados por el compilador. Un objeto de la clase tiene un puntero a su vtable. Cuando empieza la vida, que apunta el puntero vtable a la viable de la clase base. Al final del código de constructor, el compilador genera código para volver a apuntar el puntero vtable a la viable real de la clase. Esto asegura que el código constructor que llama a funciones virtuales llama al implementaciones de la clase base de esas funciones, no la anulación de la clase.

El C ++ estándar (ISO / IEC 14882-2014) decir de:

  

Las funciones miembro, incluyendo las funciones virtuales (10.3), se puede llamar   durante la construcción o destrucción (12.6.2). Cuando una función virtual   se llama directa o indirectamente de un constructor o de una   destructor, incluso durante la construcción o destrucción de la   miembros de datos no estáticos de la clase, y el objeto al que la llamada   se aplica es el objeto (llámese X) en construcción o destrucción,   la función llamada es el final en overrider o de constructores   La clase de destructor y no uno que sea redefinido en una clase derivada de más.   Si la llamada de función virtual utiliza un miembro de la clase de acceso explícita   (5.2.5) y la expresión objeto se refiere al objeto completo de x   o uno de los sub-objetos de la clase base de ese objeto, pero no x o uno de sus   subobjetos clase base, el comportamiento es Indefinido .

Así pues, no invocar funciones virtual de constructores o destructores que los intentos de poner en el objeto en construcción o destrucción, porque el orden de la construcción comienza a partir de a base de derivados y el orden de los destructores se inicia de derivado de clase base .

Por lo tanto, intentar llamar a una función clase derivada de una clase base en construcción es dangerous.Similarly, se destruye un objeto en el orden inverso de la construcción, por lo que intentar llamar a una función en una clase más derivada de un destructor puede acceder a los recursos que ya han sido puestos en libertad.

Otras respuestas ya han explicado por qué las llamadas de función virtual no funcionan como se esperaba cuando se llama desde un constructor. Me gustaría proponer en su lugar otra obra posible alrededor para conseguir el comportamiento polimórfico como desde el constructor de un tipo base.

Mediante la adición de un constructor plantilla para el tipo de base de manera que el argumento de plantilla siempre se dedujo que el tipo derivado que es posible estar al tanto de tipo inmediato del tipo derivado. A partir de ahí, puede llamar a funciones miembro static para ese tipo derivado.

Esta solución no permite funciones miembro no static a ser llamados. Mientras que la ejecución es en el constructor de la clase base, el constructor del tipo derivado ni siquiera ha tenido tiempo para ir a través de su lista de inicialización miembro. La porción de tipo derivado de la instancia de ser creado no ha comenzado está inicializando él. Y puesto que las funciones miembro no static casi seguro que interactúan con los miembros de datos que sería inusual a que desee para llamar a funciones miembro no static del tipo derivado de constructor del tipo base.

Este es un ejemplo de implementación:

#include <iostream>
#include <string>

struct Base {
protected:
    template<class T>
    explicit Base(const T*) : class_name(T::Name())
    {
        std::cout << class_name << " created\n";
    }

public:
    Base() : class_name(Name())
    {
        std::cout << class_name << " created\n";
    }


    virtual ~Base() {
        std::cout << class_name << " destroyed\n";
    }

    static std::string Name() {
        return "Base";
    }

private:
    std::string class_name;
};


struct Derived : public Base
{   
    Derived() : Base(this) {} // `this` is used to allow Base::Base<T> to deduce T

    static std::string Name() {
        return "Derived";
    }
};

int main(int argc, const char *argv[]) {

    Derived{};  // Create and destroy a Derived
    Base{};     // Create and destroy a Base

    return 0;
}

En este ejemplo se debe imprimir

Derived created
Derived destroyed
Base created
Base destroyed

Cuando se construye un Derived, el comportamiento del constructor Base depende del tipo dinámico real del objeto que está siendo construido.

En primer lugar, el objeto se crea y luego asignarle 's dirección a pointers.Constructors se llaman en el momento de la creación de objetos y se utilizan para initializ el valor de los miembros de datos. Puntero al objeto entra en el escenario después de la creación de objetos. Es por eso, C ++ no nos permite hacer constructores como virtual. .another razón es que, no hay nada como puntero al constructor, que puede apuntar a constructor virtual, porque uno de los bienes de función virtual es que puede ser utilizado por sólo los punteros.

  1. funciones virtuales se utilizan para asignar dinámicamente valor, como constructores son estáticos, por lo que no puede hacerlos virtual.

Como se ha señalado, los objetos se crean la base hacia abajo en la construcción. Cuando se construye el objeto base, el objeto derivado no existe todavía, así una anulación de función virtual no puede trabajar.

Sin embargo, esto puede ser resuelto con getters polimórficas que utilizan polimorfismo estático en lugar de las funciones virtuales si sus getters vuelven constantes, o de otra manera se pueden expresar en una función miembro estática, este ejemplo se utiliza CRTP (< a href = "https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern" rel = "nofollow noreferrer"> https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern ).

template<typename DerivedClass>
class Base
{
public:
    inline Base() :
    foo(DerivedClass::getFoo())
    {}

    inline int fooSq() {
        return foo * foo;
    }

    const int foo;
};

class A : public Base<A>
{
public:
    inline static int getFoo() { return 1; }
};

class B : public Base<B>
{
public:
    inline static int getFoo() { return 2; }
};

class C : public Base<C>
{
public:
    inline static int getFoo() { return 3; }
};

int main()
{
    A a;
    B b;
    C c;

    std::cout << a.fooSq() << ", " << b.fooSq() << ", " << c.fooSq() << std::endl;

    return 0;
}

Con el uso de polimorfismo estático, la clase base sabe qué clase getter para llamar como se proporciona la información en tiempo de compilación.

No estoy viendo la importancia de la palabra clave virtual de aquí. b es una variable estática con tipo, y su tipo se determina por compilador en tiempo de compilación. Las llamadas a funciones no hacer referencia a la viable. Cuando se construye b, el constructor de la clase padre se llama, por lo que el valor de _n se establece en 1.

Durante la llamada al constructor del objeto de la tabla de punteros de función virtual no está completamente construido. Hacer esto por lo general no le dará el comportamiento esperado. Llamar a una función virtual en esta situación puede funcionar, pero no está garantizada y debe evitarse que sea portátil y sigue el estándar de C ++.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top