Является ли типичная реализация C ++ класса Factory ошибочной? - PullRequest
4 голосов
/ 17 октября 2010

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

Итак, вот простая «типичная» фабричная реализация, она позволяет мне регистрировать новые объекты без изменения класса Factory.

//fruit.h
class Fruit
{
protected :
  int count;
public :
  Fruit(int count) : count(count) {}
  virtual void show() = 0;
};

// factory.h
/** singleton factory */
class Factory
{
  typedef Fruit* (*FruitCreateFunction)(int);
  static Factory* factory;
  std::map<std::string, FruitCreateFunction> registeredFruits;
public :
  static Factory& instance()
  {
    if (factory == NULL)
      factory = new Factory();
    return *factory;
  }
  bool registerFruit(const std::string& name, Fruit* (createFunction)(int))
  {
    registeredFruits.insert(std::make_pair(name, createFunction));
    return true;
  }
  Fruit* createFruit(const std::string& name, int count)
  {
    return registeredFruits[name](count);
  }
};

//factory.cpp
Factory* Factory::factory = NULL;

//apple.h
class Apple : public Fruit
{
  static Fruit* create(int count) { return new Apple(count); }
  Apple(int count) : Fruit(count) {}
  virtual void show() { printf("%d nice apples\n", count); };  
  static bool registered;
};

// apple.cpp
bool Apple::registered = Factory::instance().registerFruit("apple", Apple::create);

//banana.h
class Banana : public Fruit
{
  static Fruit* create(int count) { return new Banana(count); }
  Banana(int count) : Fruit(count) {}
  virtual void show() { printf("%d nice bananas\n", count); };  
  static bool registered;
};

// banana.cpp
bool Banana::registered = Factory::instance().registerFruit("banana", Banana::create);

// main.cpp
int main(void)
{
  std::vector<Fruit*> fruits;
  fruits.push_back(Factory::instance().createFruit("apple", 10));
  fruits.push_back(Factory::instance().createFruit("banana", 7));
  fruits.push_back(Factory::instance().createFruit("apple", 6));
  for (size_t i = 0; i < fruits.size(); i++)
    {
      fruits[i]->show();
      delete fruits[i];
    }
  return 0;
}

Хорошо, этот код выглядит причудливо, и он работает, но здесь приходит но:

Стандарт C ++ не позволяет мне определять порядок, в котором будут определены глобальные (статические) переменные.

У меня есть 3 статические переменные здесь

Apple::registered;
Banana::registered;
Factory::factory;

Указатель Factory::factory должен быть определен как NULL до переменной Apple (или Banana) :: зарегистрированный или метод Factory::instance будет работать с неинициализированным значением, и вести себя непредсказуемо.

Итак, что я не получаю здесь? Код действительно работает только случайно? Если да, то как мне решить проблему?

Ответы [ 3 ]

11 голосов
/ 17 октября 2010

Все глобальные данные POD гарантированно инициализируются постоянным значением до запуска любых инициализаторов.

Таким образом, в начале вашей программы, до того, как будут выполнены какие-либо вызовы регистра, и до запуска main, указатель будет равен NULL, и все bool будут ложными автоматически. Затем запускаются инициализаторы, включая ваши вызовы в реестре.

Редактировать: В частности, из стандарта (3.6.2.2: Инициализация нелокальных объектов):

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

4 голосов
/ 17 октября 2010

Все статические переменные инициализируются до запуска программы.Они устанавливаются во время компиляции и запекаются прямо в исполняемый файл.

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

В a.hpp:

static int a = 1;

в b.hpp:

extern int a;
static int b = a;

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

2 голосов
/ 17 октября 2010

Я склонен видеть, что метод «экземпляра» Фабрики реализован следующим образом:

static Factory& instance()
{
    static Factory *factory = new Factory();
    return *factory;
}

Однако дело в том, что весь доступ к экземпляру выполняется через статический метод экземпляра. Вызовы для регистрации двух классов фруктов, например, используют Factory :: instance (), чтобы получить синглтон, который гарантирует выполнение инициализатора для Factory :: factory. В моей опубликованной альтернативной реализации статическая инициализация происходит только при первом вызове метода.

Возможные проблемы с Apple ::istered и Banana ::istered зависят от того, откуда они могут быть использованы. В размещенном коде они вообще не используются. Если используется только в apple.cpp и banana.cpp соответственно, то нет проблем с порядком инициализации.

...