Имитировать статический конструктор в C ++ - PullRequest
4 голосов
/ 15 марта 2011

Это вопрос, связанный с инициализацией объектов в C ++

У меня есть группа классов (не экземпляров), унаследованная от общего базового класса, и мне нужно, чтобы они регистрировали информацию о себе в контейнере (в частности, на карте) при запуске программы.

Проблема в том, что мне нужно, чтобы он был динамичным. Контейнер определен в независимом проекте, отличном от классов. Я бы предпочел не создавать несколько жестко закодированных версий библиотеки, по одной для каждого набора классов в каждой программе, которая ее использует.

Я думал о наличии статического экземпляра специального класса в каждом из этих подклассов, который бы делал регистрацию в его конструкторе. Однако я не нашел способа гарантировать, что контейнер будет построен до постройки этих объектов.

Следует также отметить, что информация в контейнере о подклассах должна быть доступна до создания любого экземпляра этих подклассов.

Есть ли способ сделать это или имитировать статический конструктор в C ++ в целом?

Ответы [ 6 ]

1 голос
/ 15 марта 2011

Это против парадигмы ООП, но как насчет того, чтобы ваши статические члены формировали связанный список, управляемый 2 глобальными переменными? Вы могли бы сделать что-то подобное:

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

Я полагаю глобальные переменные, так как им не нужно иметь конструктор , но они являются просто чистыми данными и гарантированно будут инициализированы, когда вы начнете выполнение вашего код.

Очевидно, что это не потокобезопасно и т. Д., Но должно сделать вашу работу выполненной.

1 голос
/ 15 марта 2011

Вы одновременно описываете различные проблемы. Что касается конкретной проблемы наличия некоторой статической инициализации , простой подход заключается в создании поддельного класса, который будет выполнять регистрацию. Тогда каждый из различных классов может иметь член static const X, член должен быть определен в единице перевода, и определение будет инициировать создание экземпляра и регистрацию класса.

Это не решает сложную проблему, которая является фиаско порядка инициализации. Язык не дает никаких гарантий относительно порядка инициализации объектов в разных единицах перевода. То есть, если вы скомпилируете три единицы перевода с такими классами, нет никакой гарантии относительно относительного порядка выполнения ложного члена. Это также относится к библиотеке: нет гарантии, что контейнер, в котором вы хотите зарегистрировать ваши классы, был инициализирован, если такой контейнер является глобальным / статическим атрибутом члена.

Если у вас есть доступ к коду, вы можете изменить код контейнера для использования static local variables, и это будет шагом вперед для обеспечения порядка инициализации. В качестве эскиза возможного решения:

// 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

Теперь в этом случае нет определенного порядка регистрации классов a и b, но это может и не быть проблемой. С другой стороны, с помощью локальной статической переменной в функции registry::instance гарантируется, что инициализация синглтона будет выполняться до любого вызова методов registry::register часть первого вызова метода instance).

Если вы не можете сделать это изменение, вам в основном не повезло, и вы не можете гарантировать, что экземпляр registry будет создан раньше, чем другие атрибуты статического члена (или глобальные переменные) в других единицах перевода. Если это так, то вам придется отложить регистрацию класса до первого экземпляра и добавить код в конструктор каждого регистрируемого класса, который гарантирует, что класс зарегистрирован за до фактического построения объекта.

Это может быть или не быть решением, в зависимости от того, создает ли другой код объекты типа или нет. В частном случае фабричных функций (первая, которая пришла в голову), если ничто иное не разрешено создавать объекты типов a или b ..., то регистрация с резервным копированием для вызовов конструктора также не будет решением.

0 голосов
/ 15 марта 2011

Вы можете использовать шаблон «инициализация при первом использовании», а затем создать пустой статический экземпляр, чтобы обеспечить инициализацию как можно раньше.

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 ;
0 голосов
/ 15 марта 2011

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

Один из вариантов - создать контейнер лениво, когда к нему добавляется первое:

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

Единственный способ «имитировать» статический конструктор - это явный вызов функции для выполнения статической инициализации. Другого способа запуска предварительного кода нет, просто связавшись с модулем.

0 голосов
/ 15 марта 2011

Одна идея - передать функтор регистрация в классы. Каждый потомок будет выполнять функцию для регистрации. Этот функтор может быть передан в конструктор.

Пример:

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");
  }
};
0 голосов
/ 15 марта 2011

Это кандидат на шаблон Singleton .По сути, вы хотите, чтобы экземпляр контейнера создавался при создании экземпляра первого экземпляра подкласса.Этого можно добиться, проверив, является ли указатель-одиночка в конструкторе базового класса NULL, и если да, то создайте экземпляр контейнера.

...