Подтвердить что ты не робот

Как автоматически регистрировать класс при создании

Мне было интересно, существует ли шаблон дизайна или идиома для автоматического регистрации типа класса. Или проще, могу ли я заставить метод получать вызов класса, просто расширяя базовый класс?

Например, скажем, у меня есть базовый класс Animal и расширение классов Tiger и Dog, и у меня есть вспомогательная функция, которая выводит все классы, расширяющие Animal.

Итак, я мог бы что-то вроде:

struct AnimalManager
{
   static std::vector<std::string> names;
   static void registerAnimal(std::string name) { 
            //if not already registered
            names.push_back(name); }
};

struct Animal
{
   virtual std::string name() = 0;
   void registerAnimal() { AnimalManager::registerAnimal(name()); }
};
struct Tiger : Animal
{
   virtual std::string name() { return "Tiger"; }
};

Итак, в основном я бы сделал:

Tiger t;
t.registerAnimal();

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

Я хочу, чтобы мой class Animal расширялся в будущем, а другие могли забыть позвонить register, я ищу способы предотвратить это , кроме того, документируя этот (который я так или иначе).

PS Это просто пример, я на самом деле не реализую животных.

4b9b3361

Ответ 1

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

template<class T>
struct Animal
{
   Animal()
   { 
      reg;  //force specialization
   }
   virtual std::string name() = 0;
   static bool reg;
   static bool init() 
   { 
      T t; 
      AnimalManager::registerAnimal(t.name());
      return true;
   }
};

template<class T>
bool Animal<T>::reg = Animal<T>::init();

struct Tiger : Animal<Tiger>
{
   virtual std::string name() { return "Tiger"; }
};

В этом коде вы можете расширить только Animal, если вы его специализируете. Конструктор принудительно инициализирует элемент static reg, который, в свою очередь, вызывает метод регистрации.

EDIT: Как отметил @David Hammen в комментариях, вы не сможете иметь коллекцию объектов Animal. Однако это легко решить, если у вас есть класс без шаблона, из которого наследуется шаблон, и использовать его как базовый класс, и используйте только шаблон для расширения.

Ответ 2

Если вы настаиваете на регистрации каждого животного, почему бы не просто сделать name параметр конструктора Animal. Затем вы можете поместить проблемы регистров в конструктор Animal, и каждый производный должен будет передать действительное имя и зарегистрировать:

struct Animal
{
   Animal(std::string name){ AnimalManager::registerAnimal(name);}
}

struct Tiger : Animal
{
   Tiger():Animal("Tiger"){}
};

Ответ 3

Это типичный пример, когда вы хотите сделать какую-то учетную запись при построении объекта. Пункт 9 в Scott Meyers " Эффективный С++" дает пример этого.

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

struct Animal
{
  Animal(std::string animal_type)
  {
    AnimalManager::registerAnimal(animal_type);
  };
};

struct Dog : public Animal
{
  Dog() : Animal(animal_type()) {};


  private:      
    static std::string animal_type()
    {
      return "Dog";
    };
};

Ответ 4

Обычно я делаю это с помощью макроса.

Структуры

Unit test часто используют метод регистрации тестов, например. GoogleTest

Ответ 5

@AMCoder: Это не тот ответ, который вы хотите. Ответ, который вы хотите, отражение (например, what_am_I()) на самом деле не существует в С++.

С++ имеет довольно ограниченную форму через RTTI. Использование RTTI в базовом классе для определения "истинного" типа создаваемого объекта не будет работать. Вызов typeid(*this) в базовом классе вместо этого даст вам typeinfo для создаваемого класса.

Вы можете сделать свой класс Animal только конструктором не по умолчанию. Это отлично подходит для классов, которые производятся непосредственно из Animal, но что относительно классов, которые производятся от производного класса? Этот подход либо исключает дальнейшее наследование, либо требует, чтобы строители классов создавали несколько конструкторов, один из которых предназначен для использования производными классами.

Вы можете использовать решение Luchian CRTP, но у этого тоже есть проблемы с наследованием, и это также мешает вам иметь набор указателей на объекты Animal. Добавьте этот базовый класс без шаблонов обратно в микс, чтобы вы могли иметь коллекцию Animal, и у вас есть исходная проблема снова и снова. Ваша документация должна будет сказать, что использовать шаблон только для создания класса, полученного из Animal. Что произойдет, если кто-то этого не сделает?

Самое простое решение - это то, что вам не нравится: требовать, чтобы каждый конструктор класса, который получил от Animal, должен вызвать register_animal(). Скажите так в своей документации. Покажите пользователям вашего кода несколько примеров. Поместите комментарий перед этим примером кода, // Every constructor must call register_animal(). Люди, которые используют ваш код, будут использовать вырезать и вставлять в любом случае, поэтому у вас есть готовые решения для вырезания и вставки.

Беспокойство о том, что происходит, если люди не читают вашу документацию, - это случай преждевременной оптимизации. Требование, чтобы каждый вызов класса register_animal() в своих конструкторах был простым требованием. Каждый может понять это, и каждый может легко реализовать его. У вас гораздо больше проблем с вашими пользователями, чем с неудачной регистрацией, если ваши пользователи не могут даже выполнить эту простую инструкцию.

Ответ 6

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

class Animal {
public:
    Animal() {doStuff();}
}

Метод doStuff() может быть реализован в базовом классе для выполнения статических операций или может быть чистым виртуальным и быть реализован в производных классах.

Изменить. Как правильно указано в комментариях, виртуальные методы не могут быть вызваны в ctor.

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

class Animal {
public:
    Animal(const std::string &name) {doStuff(name);}

private:
    Animal(); // Now nobody can call it, no need to implement
}

class Dog : public Animal {
    Dog() : Animal("Dog") {}
}

Надеюсь, что поможет