我想知道是否存在自动注册类类型的设计模式或习惯用语。或者更简单,我可以通过简单地扩展基类强制一个方法来调用类吗?
例如,假设我有一个基类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这只是一个例子,我实际上并没有实施动物。
答案 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方法。
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") {}
}
希望有所帮助