Выберите шаблон C во время выполнения

#c #templates

#c #шаблоны

Вопрос:

Есть ли какой-либо способ реализовать функциональность приведенного ниже кода без создания сопоставления между строками и классами вручную?

 templatelt;class base, typename Tgt; base* f(const std::string amp;type, T amp;c) {   if(type == "ClassA") return new ClassA(c);  else if(type == "ClassB") return new ClassB(c);  // many more else if...   return nullptr; }  

Все занятия выглядят примерно так:

 class ClassA: public BaseClass { public:  std::string label="ClassA";  ...  };   

И мы можем использовать его как:

 BaseClass *b = flt;BaseClassgt;("ClassA", DifferentObject);  

Каждый новый класс приводит к новой if else строке кода. Есть ли какой-либо способ автоматизировать эту f функцию, чтобы она сама «обновлялась» при добавлении нового поддерживаемого класса? Решение должно работать для C 11.

Комментарии:

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

2. Известна ли строка только во время выполнения? В противном случае, flt;ClassAgt; кажется, лучше.

3. Если имя класса и строка равны, то вы можете создать макрос, который выполняет сопоставление. Он заменяет только структуру if…else, но нуждается в перекомпиляции, если добавляется новый класс. Является ли это приемлемым решением?

4. Да, строка известна во время выполнения. @n314159 не могли бы вы опубликовать пример макроса. Спасибо.

5. @zerocukor287 Да, это звучит хорошо для меня, но я не знаю, как написать такой макрос

Ответ №1:

Возможный макрос:

 #include lt;memorygt; #include lt;stringgt;  class BaseClass {}; class ClassA : public BaseClass {  public:  std::string label = "ClassA";  explicit ClassA(int /*unused*/) {} }; class ClassB : public BaseClass {  public:  std::string label = "ClassB";  explicit ClassB(int /*unused*/) {} }; templatelt;class base, typename Tgt; auto f(const std::string amp;type, T c) -gt; std::unique_ptrlt;basegt; { #define CASE(NAME)   if (type == "NAME") {   return std::unique_ptrlt;basegt;(new NAME(c));   }  CASE(ClassA)  CASE(ClassB) //... #undef CASE  return nullptr; // Statement at the end needed for last else! } auto main() -gt; int {  auto b = flt;BaseClassgt;("ClassA", 0); }  

Также используйте unique_ptr , так как управление необработанными указателями в памяти-это ЗЛО.

Комментарии:

1. Спасибо за это. Это все еще требует изменения функции f при введении нового класса. Есть ли какой-нибудь способ автоматизировать его дальше? Как-то найти все производные классы базового класса?

2. Нет, это явно невозможно, поскольку новые производные классы могут быть определены где угодно, даже в совершенно несвязанных единицах перевода, которые не могли бы изменить f.

Ответ №2:

В том случае, если имя класса равно строке, вы можете упростить свой код с помощью следующего макроса:

 #define STRING_TO_CLASS (className) if(type == "className") return new className(c);  templatelt;class base, typename Tgt; base* f(const std::string amp;type, T amp;c) {   STRING_TO_CLASS(ClassA)  STRING_TO_CLASS(ClassB)   return nullptr; }  

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

 templatelt;class base, typename Tgt; base* f(const std::string amp;type, T amp;c) {   if(type == "ClassA") return new ClassA(c);  if(type == "ClassB") return new ClassB(c);   return nullptr; }  

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

Комментарии:

1. Спасибо за это. Этот макрос немного облегчает жизнь, но все же «регистрацию» классов я бы описал как ручную.

2. Вам не нужна «автоматическая» регистрация. Кто-нибудь может пройти std::ofstream мимо и начать перезаписывать файлы!

Ответ №3:

Вы можете использовать шаблон реестра следующим образом:

 #include lt;mapgt; #include lt;functionalgt; #include lt;stringgt;  templatelt; typename T, typename X gt; using Factory = std::functionlt; T* ( Xamp; ) gt;;   templatelt; typename Base, typename X gt; struct Registry {   using Map = std::maplt;std::string,Factorylt;Base,Xgt; gt;;  static Map registry;   templatelt; typename T gt;  struct Register {  Register( const std::stringamp; name ) {  registry[ name ] = []( Xamp; x ) -gt; T* { return new T(x); };  }  }; };  templatelt; typename Base, typename X gt; Base* factory(const std::string amp;type, X amp;c ) {  auto it = Registrylt;Base,Xgt;::registry.find( type );  if ( it!=Registrylt;Base,Xgt;::registry.end() ) {  return (it-gt;second)(c);  }  return nullptr; }  struct X {};  struct A {  A( Xamp; x ) {};  virtual ~A() {} };  struct B : public A {  B( Xamp; x ) : A(x) {}; };  struct C : public A {  C( Xamp; x ) : A(x) {}; };  struct D : public B {  D( Xamp; x ) : B(x) {}; };    // Register class templatelt;gt; Registrylt;A,Xgt;::Map Registrylt;A,Xgt;::registry{}; Registrylt;A,Xgt;::Registerlt;Bgt; regB( "B" ); Registrylt;A,Xgt;::Registerlt;Cgt; regC( "C" ); Registrylt;A,Xgt;::Registerlt;Dgt; regD( "D" );  #include lt;iostreamgt; int main() {  X x;  A* ptr = factorylt;A,Xgt;( "B", x );  B* bptr = dynamic_castlt;B*gt;( ptr );  if ( bptr!= nullptr ) {  std::cout lt;lt; "Success!" lt;lt; std::endl;  return 0;  }  std::cout lt;lt; "Failed!" lt;lt; std::endl;  return 1; }  

Ответ №4:

Правильный шаблон для использования здесь-это шаблон «Абстрактная фабрика».

Может быть, вы сможете это посмотреть.

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

 #include lt;iostreamgt; #include lt;mapgt; #include lt;utilitygt; #include lt;anygt;   // Some demo classes ---------------------------------------------------------------------------------- struct Base {  Base(int d) : data(d) {};  virtual ~Base() { std::cout lt;lt; "Destructor Basen"; }  virtual void print() { std::cout lt;lt; "Print Basen"; }  int data{}; }; struct Child1 : public Base {  Child1(int d, std::string s) : Base(d) { std::cout lt;lt; "Constructor Child1 " lt;lt; d lt;lt; " " lt;lt; s lt;lt; "n"; }  virtual ~Child1() { std::cout lt;lt; "Destructor Child1n"; }  virtual void print() { std::cout lt;lt; "Print Child1: " lt;lt; data lt;lt; "n"; } }; struct Child2 : public Base {  Child2(int d, char c, long l) : Base(d) { std::cout lt;lt; "Constructor Child2 " lt;lt; d lt;lt; " " lt;lt; c lt;lt; " " lt;lt; l lt;lt; "n"; }  virtual ~Child2() { std::cout lt;lt; "Destructor Child2n"; }  virtual void print() { std::cout lt;lt; "Print Child2: " lt;lt; data lt;lt; "n"; } }; struct Child3 : public Base {  Child3(int d, long l, char c, std::string s) : Base(d) { std::cout lt;lt; "Constructor Child3 " lt;lt; d lt;lt; " " lt;lt; l lt;lt; " " lt;lt; c lt;lt; " " lt;lt; s lt;lt; "n"; }  virtual ~Child3() { std::cout lt;lt; "Destructor Child3n"; }  virtual void print() { std::cout lt;lt; "Print Child3: " lt;lt; data lt;lt; "n"; } };    using UPTRB = std::unique_ptrlt;Basegt;;   template lt;class Child, typename ...Argsgt; UPTRB createClass(Args...args) { return std::make_uniquelt;Childgt;(args...); }  // The Factory ---------------------------------------------------------------------------------------- template lt;class Key, class Objectgt; class Factory {  std::maplt;Key, std::anygt; selector; public:  Factory() : selector() {}  Factory(std::initializer_listlt;std::pairlt;const Key, std::anygt;gt; il) : selector(il) {}   templatelt;typename Functiongt;  void add(Key key, Functionamp;amp; someFunction) { selector[key] = std::any(someFunction); };   template lt;typename ... Argsgt;  Object create(Key key, Args ... args) {  if (selector.find(key) != selector.end()) {  return std::any_castlt;std::add_pointer_tlt;Object(Args ...)gt;gt;(selector[key])(args...);  }  else return nullptr;  } };  int main() {  // Define the factory with an initializer list  Factorylt;int, UPTRBgt; factory{  {1, createClasslt;Child1, int, std::stringgt;},  {2, createClasslt;Child2, int, char, longgt;}  };   // Add a new entry for the factory  factory.add(3, createClasslt;Child3, int, long, char, std::stringgt;);    // Some test values  std::string s1(" Hello1 "); std::string s3(" Hello3 ");  int i = 1; const int ci = 1; intamp; ri = i; const intamp; cri = i; intamp;amp; rri = 1;   UPTRB b1 = factory.create(1, 1, s1);  UPTRB b2 = factory.create(2, 2, '2', 2L);  UPTRB b3 = factory.create(3, 3, 3L, '3', s3);   b1-gt;print();  b2-gt;print();  b3-gt;print();  b1 = factory.create(2, 4, '4', 4L);  b1-gt;print();  return 0; }