如何在创建时自动注册一个类

时间:2012-04-26 11:44:41

标签: c++ design-patterns idioms

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

例如,假设我有一个基类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这只是一个例子,我实际上并没有实施动物。

6 个答案:

答案 0 :(得分:16)

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

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个对象的集合。但是,通过使用模板继承的非模板类并将其用作基类,并且仅使用模板进行扩展,可以很容易地解决这个问题。

答案 1 :(得分:8)

如果你坚持要注册每只动物,为什么不让name成为Animal构造函数的参数。然后你可以将注册问题放到Animal构造函数中,每个派生的都必须传递有效的名称并注册:

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

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

答案 2 :(得分:4)

这是一个典型的例子,你想在构造一个对象时做一些簿记。 Scott Meyers的第9项“Effective C++”举了一个例子。

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

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

答案 3 :(得分:2)

通常我用宏来做这件事。

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

答案 4 :(得分:2)

@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()是一个简单的要求。每个人都可以理解它,每个人都可以轻松实现它。如果您的用户甚至无法遵循这条简单的说明,那么您的用户手中的麻烦比失败的用户要大得多。

答案 5 :(得分:1)

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

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

希望有所帮助