Автоматическая регистрация во время компиляции шаблонов классов в C ++ - PullRequest
9 голосов
/ 06 октября 2011

Я ищу абстрактную фабрику для шаблонов классов, где классы регистрируются автоматически во время статической инициализации.Для обычных (не шаблонных) классов решение достаточно простое, используя статические члены.Вот пример (довольно упрощенного) решения, которое работает просто отлично:

#include <cassert>
#include <iostream>

class Base {
 public:
  virtual size_t id() const = 0;
  virtual const char* name() const = 0;
  virtual ~Base() {}
};

typedef Base* (*CreateFunc)(void);

class SimpleFactory {
 private:
  static const size_t NELEM = 2;
  static size_t id_;
  static CreateFunc creators_[NELEM];

 public:
  static size_t registerFunc(CreateFunc creator) {
    assert(id_ < NELEM);
    assert(creator);
    creators_[id_] = creator;
    return id_++;
  }

  static Base* create(size_t id) { assert(id < NELEM); return (creators_[id])(); }
};

size_t SimpleFactory::id_ = 0;
CreateFunc SimpleFactory::creators_[NELEM];


class D1 : public Base {
 private:
  static Base* create() { return new D1; }
  static const size_t id_;

 public:
  size_t id() const { return id_; }
  const char* name() const { return "D1"; }
};

const size_t D1::id_ = SimpleFactory::registerFunc(&create);

class D2 : public Base {
 private:
  static Base* create() { return new D2; }
  static const size_t id_;

 public:
  size_t id() const { return id_; }
  const char* name() const { return "D2"; }
};

const size_t D2::id_ = SimpleFactory::registerFunc(&create);

int main() {
  Base* b1 = SimpleFactory::create(0);
  Base* b2 = SimpleFactory::create(1);
  std::cout << "b1 name: " << b1->name() << "\tid: " << b1->id() << "\n";
  std::cout << "b2 name: " << b2->name() << "\tid: " << b2->id() << "\n";
  delete b1;
  delete b2;
  return 0;
}

У меня есть вопрос, как заставить его работать, когда материал, который я хочу зарегистрировать / создать, больше похож на:

template <typename T> class Base...
template <typename T> class D1 : public Base<T> ...

Лучшая идея, которую я могу придумать, - это также создать шаблон фабрики, например:

 template <typename T>
 class SimpleFactory {
 private:
  static const size_t NELEM = 2;
  static size_t id_;
  typedef Base<T>* Creator;
  static Creator creators_[NELEM];
...(the rest remains largely the same)

Но мне интересно, есть ли лучший способ или кто-то реализовал такойшаблон до.

РЕДАКТИРОВАТЬ: вернувшись к этой проблеме несколько лет спустя (и с помощью шаблонов с переменными значениями), я могу значительно приблизиться к тому, что хочу, просто «зарегистрировав» функции или, скорее, классы, в качестве параметров шаблона дляфабрика.Это выглядело бы примерно так:

#include <cassert>

struct Base {};

struct A : public Base {
  A() { std::cout << "A" << std::endl; }
};

struct B : public Base {
  B() { std::cout << "B" << std::endl; }
};

struct C : public Base {
  C() { std::cout << "C" << std::endl; }
};

struct D : public Base {
  D() { std::cout << "D" << std::endl; }
};


namespace {
  template <class Head>
  std::unique_ptr<Base>
  createAux(unsigned id)
  {
    assert(id == 0);
    return std::make_unique<Head>();
  }

  template <class Head, class Second, class... Tail>
  std::unique_ptr<Base>
  createAux(unsigned id)
  {
    if (id == 0) {
      return std::make_unique<Head>();
    } else {
      return createAux<Second, Tail...>(id - 1);
    }
  }
}

template <class... Types>
class LetterFactory {
 public:
  std::unique_ptr<Base>
  create(unsigned id) const
  {
    static_assert(sizeof...(Types) > 0, "Need at least one type for factory");
    assert(id < sizeof...(Types));
    return createAux<Types...>(id);
  }
};

int main() {
  LetterFactory<A, B, C, D> fac;
  fac.create(3);
  return 0;
}

Теперь это просто упрощенный прототип, так что не забывайте о линейной сложности create ().Однако основным недостатком этого проекта является то, что он не учитывает никаких параметров конструктора.В идеале я мог бы зарегистрировать не только классы, которые нужны фабрике для создания, но также и типы, которые каждый класс принимает в своем конструкторе, и позволить create () принимать их по-разному.Кто-нибудь когда-нибудь делал что-то подобное раньше?

Ответы [ 2 ]

2 голосов
/ 06 октября 2011

Я опубликовал ответ на похожую проблему в GameDev, но решение не компилируется. Вы можете проверить это здесь:
> https://gamedev.stackexchange.com/questions/17746/entity-component-systems-in-c-how-do-i-discover-types-and-construct-components/17759#17759

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

0 голосов
/ 06 октября 2011

Выполнение более простых вещей будет меньше ломаться и сделает ваши намерения очевидными.

int main() {
  RegisterConcreteTypeFoo();
  RegisterConcreteTypeBar();
  // do stuff...
  CleanupFactories();
  return 0;
}

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

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

Меньше магии = проще, дешевле обслуживание.

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

...