如何在创建时自动注册类

How to automatically register a class on creation

本文关键字:注册 创建      更新时间:2023-10-16

我想知道是否存在自动注册类类型的设计模式或习语。或者更简单地说,我可以通过简单地扩展基类来强制调用类的方法吗?

例如,假设我有一个基类Animal和扩展类TigerDog,并且我有一个辅助函数,可以打印出所有扩展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 这只是一个例子,我实际上并没有实现动物。

您确实可以使用奇怪的递归模板习惯用法来做到这一点。它不需要任何扩展编译器无法强制执行的类的人:

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,进而调用 register 方法。

编辑:正如@David Hammen在评论中指出的那样,您将无法拥有Animal对象的集合。但是,这可以通过拥有一个非模板类轻松解决,模板从该类继承并将其用作基类,并且仅使用模板进行扩展。

如果你坚持每个动物都应该注册,为什么不把name Animal构造函数的参数。然后,您可以将寄存器问题交给构造函数Animal每个派生都必须传递有效的名称和寄存器:

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

这是一个典型的示例,您希望在构造对象时执行某种簿记。斯科特·迈耶斯(Scott Meyers)"有效C++"中的第9项给出了一个例子。

基本上,您将所有簿记内容移动到基类中。 派生类显式构造基类并传递基类构造所需的信息。例如:

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";
    };
};

通常我用宏来做这件事。

单元测试框架通常采用注册测试的技术,例如GoogleTest

@AMCoder:这不是你想要的答案。你想要的答案,反射(例如,what_am_I())实际上并不存在C++。

C++通过RTTI的形式相当有限。在基类中使用 RTTI 来确定正在构造的对象的"true"类型是行不通的。在基类中调用typeid(*this)将改为为您提供正在构造的类的typeinfo

可以使类Animal仅具有非默认构造函数。这对于直接从 Animal 派生的类工作正常,但是从派生类派生的类呢?此方法要么排除进一步继承,要么要求类生成器创建多个构造函数,其中一个构造函数供派生类使用。

您可以使用 Luchian 的 CRTP 解决方案,但这也存在继承问题,并且它还阻止您拥有指向Animal对象的指针集合。将该非模板基类添加回组合中,以便您可以拥有 Animal 的集合,并且您再次遇到原始问题。您的文档必须说仅使用该模板来创建一个派生自Animal的类。如果有人不这样做会怎样?

最简单的解决方案是你不喜欢的解决方案:要求派生自Animal类的每个构造函数都必须调用register_animal()。在您的文档中这样说。向代码的用户展示一些示例。在示例代码前面添加注释,// Every constructor must call register_animal().使用您的代码的人无论如何都会使用剪切和粘贴,因此手头有一些可剪切和粘贴的解决方案。

担心如果人们不阅读您的文档会发生什么是过早优化的情况。要求每个类调用register_animal()在其构造函数中是一项简单的要求。每个人都可以理解它,每个人都可以轻松实现它。如果您的用户甚至不能遵循这个简单的说明,那么您与用户之间的麻烦比注册失败要大得多。

您可以在基类构造函数中调用一个方法,每次实例化派生类时都会调用该方法,如下所示:

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") {}
}

希望有帮助