我有一个基类,我的很多类都来自于:
class BaseSystem
{
public:
virtual void doThing() = 0;
}
我希望能够对类的所有派生类型进行标记,以便在应用程序启动时找到它们。在c#中,我会通过反射来寻找属性,或者只是寻找从基类派生的任何东西。
有没有类似的方法可以在c ++中做到这一点呢?我可以用某种东西制作类,并在编译时发现它们,并在向量中为它们创建一个实例?
编辑: 更多关于我要解决的问题的背景。
我正在创建一个静态库,使程序员可以实现实体组件系统模式来形成游戏引擎的基础。这个想法是库具有一个基类,他们可以从中实现系统,并且系统管理员将能够发现它们,然后在游戏开始时运行它们。
答案 0 :(得分:1)
正如其他人在对您的问题的评论中指出的那样,通常无法在C ++编译时检测特定基类的所有现有派生类。
但是,如果您只需要一种机制来避免一个地方了解所有现有的派生类,那么您可以做一些事情,尽管不是在编译时。
这个想法是使用静态成员变量的初始化(保证在执行main
之前发生)来在派生的注册表中注册派生的类。
这样的注册表看起来像这样:
class derived_registry
{
public:
static std::size_t number_of_instances()
{
return _instances.size();
}
static base* instance(std::size_t const index)
{
assert(index < _instances.size());
return _instances[index].get();
}
template <typename T, std::enable_if_t<std::is_base_of_v<base, T>, int> = 0>
static std::size_t register_derived_class(std::unique_ptr<T> instance)
{
auto const index = _instances.size();
_instances.emplace_back(std::move(instance));
return index;
}
private:
static std::vector<std::unique_ptr<base>> _instances;
};
inline std::vector<std::unique_ptr<base>> derived_registry::_instances;
任何base
派生类现在都必须通过调用derived_registry::register_derived_class
来初始化一个静态成员变量,例如注册一个自己。像这样:
// in derived1.h
class derived1 : public base
{
public:
derived1();
void do_something() override;
private:
static std::size_t _index;
};
// in derived1.cpp
std::size_t derived1::_index = derived_registry::register_derived_class(std::make_unique<derived1>());
derived1::derived1()
: base{}
{
}
void derived1::do_something()
{
std::cout << "derived 1\n";
}
这也说明了为什么将derived_registry::_instances
向量定义为inline
变量很重要:我们需要确保derived_registry::register_derived_class
仅在derived_registry::_instances
已经被调用之后才被调用初始化。最简单的方法是使用以下事实:当一个转换单元中定义了多个具有静态存储持续时间的变量时,可以保证按照定义的顺序对其进行初始化。由于我们已经在头文件中定义了derived_registry::_instances
,因此可以保证derived_registry::_instances
在derived1::_index
之前,因此在调用derived_registry::register_derived_class
之前被初始化。
您可以在wandbox上看到这种方法的作用。
尽管上面的实现可行,但是它很麻烦,而且仍然有可能有人添加了新的派生类,却忘记了注册它。
要简化注册部分,您可以使用this question的答案中所述的CRTP模式,StoryTeller在评论中链接到您的问题。
尽管这可以使注册非常容易,但是只有当每个派生类从CRTP基继承或实现注册本身时,注册表仍然可以正常工作,这很容易忘记。为了确保别无选择,只能继承自CRTP基类,您还可以将base
的构造方法设为私有,并使CRTP基类成为唯一的朋友。这样一来,如果不注册新的派生类,就不会偶然地直接从base
继承。