Pregunta

Esta es una pregunta relacionada con la inicialización de objetos en C ++

Tengo un grupo de clases (no instancias), heredé de una clase base común, y necesito que registren información sobre sí mismas en un contenedor (específicamente un mapa) cuando comienza el programa.

El problema es que necesito que sea dinámico. El contenedor se define en un proyecto independiente, diferente de las clases. Preferiría evitar hacer múltiples versiones codificadas de la biblioteca, una para cada conjunto de clases en cada programa que lo use.

Pensé en tener una instancia estática de una clase especial en cada una de estas subclases, que haría el registro en su constructor. Sin embargo, no he encontrado ninguna forma de garantizar que el contenedor se haya construido antes de la construcción de estos objetos.

También debo tener en cuenta que la información en el contenedor sobre las subclases debe estar disponible antes de que se cree cualquier instancia de estas subclases.

¿Hay alguna manera de hacer esto o imitar un constructor estático en C ++ en general?

¿Fue útil?

Solución

Estás describiendo diferentes problemas a la vez. Sobre el tema particular de tener algún tipo de inicialización estática, un enfoque simple es crear una clase falsa que realizará el registro. Entonces cada una de las diferentes clases podría tener un static const X Miembro, el miembro deberá definirse en una unidad de traducción, y la definición activará la instancia de la instancia y el registro de la clase.

Esto no aborda el problema difícil, que es el fiasco de la orden de inicioilización. El idioma no proporciona ninguna garantía sobre el orden de inicialización de objetos en diferentes unidades de traducción. Es decir, si compila tres unidades de traducción con tales clases, no hay garantía sobre el orden relativo de ejecución del miembro falso. Eso también se aplica a la biblioteca: no hay garantía de que el contenedor en el que desea registrar sus clases se haya inicializado, si dicho contenedor es un atributo de miembro global/estático.

Si tiene acceso al código, puede modificar el código del contenedor para usar static local variables, y eso será un paso adelante para garantizar el orden de inicialización. Como bosquejo de una posible solución:

// registry lib
class registry { // basically a singleton
public:
   static registry& instance() { // ensures initialization in the first call
      static registry inst;
       return inst;
   }
// rest of the code
private:
   registry(); // disable other code from constructing elements of this type
};
// register.h
struct register {
   template <typename T>
   register( std::string name ) {
       registry::instance().register( name, T (*factory)() ); // or whatever you need to register
   }
};
// a.h
class a {
public:
   static a* factory();
private:
   static const register r;
};
// a.cpp
const register a::r( "class a", a::factory );
// b.h/b.cpp similar to a.h/a.cpp

Ahora en este caso no hay orden definido entre el registro del a y b clases, pero eso podría no ser un problema. Por otro lado, usando un variable estática local en el registry::instance función La inicialización del singleton está garantizado que se realizará antes de cualquier llamada al registry::register métodos (como parte de la primera llamada al instance método).

Si no puede hacer ese cambio, básicamente no tiene suerte y no puede garantizar que el registry se instanciará antes de los otros atributos de miembros estáticos (o globales) en otras unidades de traducción. Si ese es el caso, tendrá que posponer el registro de la clase a la primera instancia y agregar código al constructor de cada clase que se registre que asegure que la clase esté registrada antes de Construcción real del objeto.

Esto podría o no ser una solución, dependiendo de si otro código crea objetos del tipo o no. En el caso particular de las funciones de fábrica (primero que vino a la mente), si no se permite que nada más cree objetos de tipos a o b... Entonces el registro de respaldo de piggy en las llamadas de constructor tampoco será una solución.

Otros consejos

Está en contra del paradigma de OOP, pero ¿qué tal si sus miembros estáticos forman una lista vinculada guiada por 2 variables globales? Podrías hacer algo así:

ClassRegistrator *head=NULL;
ClassRegistrator *tail=NULL;

struct ClassRegistrator {
    ... //data that you need
    ClassRegistrator *next;
    ClassRegistrator(classData ...) {
      if (head==NULL) head=tail=this;
      else {
        tail->next=this;
        tail=this;
      }
      ... //do other stuff that you need for registration
    }
};


class MyClass { //the class you want to register
    static ClassRegistrator registrator;
}

ClassRegistrator MyClass::registrator(...); //call the constructor

yo creer las variables globales, ya que no necesitan tener un constructor, pero son solo datos puros, se garantiza que ya se inicializarán cuando comience la ejecución de su código.

Obviamente, esto no es seguro de hilo, etc., pero debería hacer su trabajo.

Este es un candidato para el Patrón singleton. Básicamente, desea que el contenedor sea instanciado cuando se instancia la primera instancia de una subclase. Esto se puede facilitar verificando si el puntero singleton es nulo en el constructor de clase base, y si es así, instanciar el contenedor.

Una idea es pasar un registro functor a las clases. Cada descendiente ejecutaría la función para registrarse. Este functor podría pasarse en el constructor.

Ejemplo:

struct Registration_Interface
{
  virtual void operator() (const std::string& component_name) = 0;
};

struct Base
{
};

struct Child1
  : public Base
{
  Child(Registration_Interface& registration_ftor)
  {
     //...
     registration_ftor("Child1");
  }
};

Ver: http://www.parashift.com/c++faq-lite/ctors.html#faq-10.14

Una opción es construir el contenedor perezosamente, cuando se le agrega lo primero:

  void AddToContainer(...) {
    // Will be initialized the first time this function is called.
    static Container* c = new Container();
    c->Add(...);
  }

La única forma de "imitar" un constructor estático es llamar explícitamente una función para realizar su inicialización estática. No hay otra forma de ejecutar el código previo al main simplemente vinculando en un módulo.

Puede usar un patrón de "inicializar en primer uso" y luego instanciar una instancia estática ficticia para garantizar la inicialización lo antes posible.

class cExample
{
    public :
       cExample() ;

       // Static functions here

    private :
        static bool static_init ;

        // other static members here
}

cExample::static init = false ;

cExample::cExample()
{
    // Static initialisation on first use
    if( !static_init )
    {
        // initialise static members
    }

    // Instance initialisation here (if needed)
}

// Dummy instance to force initialisation before main() (if necessary)
static cExample force_init ;
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top