15

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

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

4

6 回答 6

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

于 2012-04-26T12:41:35.813 回答
8

如果你坚持每只动物都应该注册,为什么不直接做name一个构造函数的参数Animal。然后您可以将注册问题提交给Animal构造函数,并且每个派生都必须传递有效名称并注册:

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

struct Tiger : Animal
{
   Tiger():Animal("Tiger"){}
};
于 2012-04-26T12:31:11.890 回答
4

这是一个典型的示例,您希望在构造对象时进行某种簿记。Scott Meyers “ Effective 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";
    };
};
于 2012-04-26T12:35:56.130 回答
2

通常我用宏来做到这一点。

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

于 2012-04-26T12:29:52.303 回答
2

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

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

你可以让你的类Animal只有一个非默认的构造函数。这对于直接从 派生的类很有效Animal,但是从派生类派生的类呢?这种方法要么排除进一步的继承,要么要求类构建器创建多个构造函数,其中一个供派生类使用。

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

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

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

于 2012-04-26T14:21:06.227 回答
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") {}
}

希望有帮助

于 2012-04-26T12:31:13.933 回答