2009-07-20 10 views
9

Debido a los conocidos problemas con llamar a métodos virtuales desde dentro de constructores y destructores, comúnmente termino con clases que necesitan un método de configuración final para ser llamado justo después de su constructor, y un método de predescompresión para ser llamado solo antes de su destructor, así:¿Existe alguna forma automática de implementar llamadas a métodos virtuales posteriores al constructor y predestructor?

MyObject * obj = new MyObject; 
obj->Initialize(); // virtual method call, required after ctor for (obj) to run properly 
[...] 
obj->AboutToDelete(); // virtual method call, required before dtor for (obj) to clean up properly 
delete obj; 

esto funciona, pero lleva consigo el riesgo de que la persona que llama se olvide de llamar a uno o ambos de estos métodos en los momentos adecuados.

Entonces, la pregunta es: ¿hay alguna forma en C++ para llamar esos métodos automáticamente, para que la persona que llama no tenga que recordar llamarlos? (Supongo que no, pero pensé en preguntar de todos modos en caso de que haya alguna manera inteligente de hacerlo)

+0

¿Qué problema tienes con los destructores? – peterchen

+4

Quizás deba describir su problema real, tal vez en realidad no * necesita * estas llamadas ... – peterchen

+2

si "comúnmente" necesita llamar a métodos virtuales desde controladores o dtors, parece que tiene un problema de diseño importante. ¿Puedes dar un ejemplo de una clase donde esto es necesario? Lo más probable es que haya una solución más simple. (Como de costumbre, esperaría que RAII solucione el problema. Delegue el problema en una o más variables miembro, con sus propios ctors/dtors cada uno haciendo su parte de inicialización/desmontaje. – jalf

Respuesta

1

El principal problema con la adición de post-constructores en C++ es que nadie ha establecido cómo hacer frente a post-post-constructores, post-post-post-constructores, etc.

la teoría subyacente es que los objetos tienen invariantes. Esta invariante es establecida por el constructor. Una vez que se ha establecido, se pueden llamar a los métodos de esa clase. Con la introducción de diseños que requerirían constructores posteriores, está introduciendo situaciones en las que los invariantes de clase no se establecen una vez que el constructor se ha ejecutado. Por lo tanto, sería igualmente inseguro permitir llamadas a funciones virtuales por parte de constructores posteriores, e inmediatamente se pierde el único beneficio aparente que parecían tener.

medida que su ejemplo muestra (probablemente sin que te des cuenta), no son necesarios:

MyObject * obj = new MyObject; 
obj->Initialize(); // virtual method call, required after ctor for (obj) to run properly 

obj->AboutToDelete(); // virtual method call, required before dtor for (obj) to clean up properly 
delete obj; 

Vamos a mostrar por qué no se necesitan estos métodos. Estas dos llamadas pueden invocar funciones virtuales desde MyObject o una de sus bases. Sin embargo, MyObject::MyObject() también puede llamar a esas funciones de forma segura. No hay nada que ocurra después de MyObject::MyObject() devuelve que haría obj->Initialize() seguro. Entonces obj->Initialize() está mal o su llamada se puede mover a MyObject::MyObject(). La misma lógica se aplica en reversa al obj->AboutToDelete(). El destructor más derivado se ejecutará primero y todavía puede llamar a todas las funciones virtuales, incluido AboutToDelete().

+5

Excepto cuando Initialize() se vuelve a implementar en una subclase de MyObject, y necesito llamar a la implementación de la subclase, no a MyObject :: Initialize(). Llamado desde el constructor MyObject, no hace lo que necesito que haga. (AboutToDelete() tiene el mismo problema cuando se llama desde MyObject :: ~ MyObject()) De todos modos, "lo que ocurre después de que MyObject :: MyObject() returns" es la ejecución de los constructores de subclase ... esos necesitan suceder antes de que se ejecute Initialize(). La lógica se invierte para AboutToDelete(), que debe ejecutarse antes de ejecutar cualquier destructores de subclase. –

+0

Eso obviamente no es el caso aquí ya que 'new MyObject' precede directamente a la llamada. Y su contraejemplo simplemente cambia los nombres. El constructor más derivado se ejecuta en último lugar, cuando se han establecido todas las invariantes y se pueden llamar todas las funciones virtuales. Ese ctor todavía puede llamar a Initialize() – MSalters

+4

El constructor más derivado no puede llamar a Initialize() de forma segura, porque no puede estar seguro de que sea el constructor más derivado. Muy bien podría ser que otra clase lo haya subclasificado, y en ese caso Inicializar() se llamaría demasiado pronto. –

2

Utilicé un método de fábrica Create() muy cuidadosamente diseñado (miembro estático de cada clase) llamar a un constructor y un par de inicializadores en el mismo orden en que C# inicializa los tipos. Devolvió shared_ptr a una instancia del tipo, lo que garantiza una asignación de montón. Resultó confiable y consistente a lo largo del tiempo.

El truco: me genera mi C++ declaraciones de clases de XML ...

+0

Supongo que proporcionó un eliminador personalizado para 'shared_ptr', que incluía la llamada a la lógica de predestrucción. –

3

Lo mejor que puedo pensar es para que usted pueda implementar su propio puntero inteligente con un método Create estática que las noticias de una instancia y llama Inicializar y en su destructor llama a AboutToDelete y luego elimina.

9

Si bien no existe una forma automática, podría forzar la mano de los usuarios al negar a los usuarios el acceso al destructor en ese tipo y declarar un método de eliminación especial. En este método, puede hacer las llamadas virtuales que desee. La creación puede tomar un enfoque similar que un método de fábrica estático.

class MyObject { 
    ... 
public: 
    static MyObject* Create() { 
    MyObject* pObject = new MyObject(); 
    pObject->Initialize(); 
    return pObject; 
    } 
    Delete() { 
    this->AboutToDelete(); 
    delete this; 
    } 
private: 
    MyObject() { ... } 
    virtual ~MyObject() { ... } 
}; 

Ahora no es posible llamar a "eliminar obj;" a menos que el sitio de llamadas tenga acceso a miembros privados de MyObject.

+0

¡Me gusta eso, +1! –

+1

Destructor puede (y debe) ser virtual, ¿por qué pasar por el trabajo extra? –

+0

@dribeas, lo actualicé para hacerlo virtual. – JaredPar

1

A excepción de la idea de JavedPar para el método de predestrucción, no existe una solución prefabricada para realizar fácilmente la construcción/destrucción en dos fases en C++. La forma más obvia de hacer esto es seguir la respuesta más común a los problemas en C++: "Agregar otra capa de indirección". Puede envolver objetos de esta jerarquía de clases dentro de otro objeto. Los constructores/destructor de ese objeto podrían llamar a estos métodos. Mire en el lenguaje envolvente de cartas de Couplien, por ejemplo, o use el enfoque de puntero inteligente ya sugerido.

2

http://www.research.att.com/~bs/wrapper.pdf Este papel de Stroustrup resolverá su problema.

He probado esto en VS 2008 y en UBUNTU contra el compilador g ++. Funcionó bien

#include <iostream> 

using namespace std; 

template<class T> 

class Wrap 
{ 
    typedef int (T::*Method)(); 
    T* p; 
    Method _m; 
public: 
    Wrap(T*pp, Method m): p(pp), _m(m) { (p->*_m)(); } 
    ~Wrap() { delete p; } 
}; 

class X 
{ 
public: 
    typedef int (*Method)(); 
    virtual int suffix() 
    { 
     cout << "X::suffix\n"; 
     return 1; 
    } 

    virtual void prefix() 
    { 
     cout << "X::prefix\n"; 
    } 

    X() { cout << "X created\n"; } 

    virtual ~X() { prefix(); cout << "X destroyed\n"; } 

}; 

class Y : public X 
{ 
public: 
    Y() : X() { cout << "Y created\n"; } 
    ~Y() { prefix(); cout << "Y destroyed\n"; } 
    void prefix() 
    { 
     cout << "Y::prefix\n"; 
    } 

    int suffix() 
    { 
     cout << "Y::suffix\n"; 
     return 1; 
    } 
}; 

int main() 
{ 
    Wrap<X> xx(new X, &X::suffix); 
    Wrap<X>yy(new Y, &X::suffix); 
} 
+0

+1 Artículo muy interesante. Sin embargo, parece que solo envuelve los métodos estándar, no los constructores y los destructores. – iain

0

Aún no he visto la respuesta, pero las clases base son solo una forma de agregar código en una jerarquía de clases. También puede crear clases diseñadas para ser añadido a la otra cara de la jerarquía:

template<typename Base> 
class Derived : public Base { 
    // You'd need C++0x to solve the forwarding problem correctly. 
    Derived() : Base() { 
     Initialize(); 
    } 
    template<typename T> 
    Derived(T const& t): Base(t) { 
     Initialize(); 
    } 
    //etc 
private: 
    Initialize(); 
}; 
1

me quedé con el mismo problema, y ​​después de un poco de investigación, creo que no hay ninguna solución estándar.

Las sugerencias que más me gustaron fueron las que figuran en Aleksandrescu et al. libro "C++ estándares de codificación" en el artículo 49.

Citando ellos (uso justo), tiene varias opciones:

  1. Sólo documentarla que se necesita un segundo método, como lo hizo.
  2. tienen otro estado interno (un booleano) que las banderas si post-construcción ha tenido lugar
  3. Use semántica de clase virtual, en el sentido de que el constructor de la clase más derivada decide qué clase base para utilizar
  4. Uso una función de fábrica.

Consulte su libro para más detalles.

1

Puede utilizar la plantilla de funciones estáticas en la clase. Con ctor/dtor privado. Ejecutar en la comunidad vs2015

class A { 
    protected: 
    A() {} 
     virtual ~A() {} 
     virtual void onNew() = 0; 
     virtual void onDelete() = 0; 
    public: 

     void destroy() { 
      onDelete(); 
      delete this; 
     } 

     template <class T> static T* create() { 
      static_assert(std::is_base_of<A, T>::value, "T must be a descendant of A"); 
      T* t = new T(); 
      t->onNew(); 
      return t; 
     } 
    }; 

class B: public A { 
    friend A; 

    protected: 
      B() {} 
      virtual ~B() {} 

      virtual void onNew() override { 
      } 

      virtual void onDelete() override { 
      } 
}; 

int main() { 
    B* b; 
    b = A::create<B>(); 
    b->destroy(); 
} 
Cuestiones relacionadas