编译时派生类发现

时间:2018-09-16 13:18:54

标签: c++ c++17

我有一个基类,我的很多类都来自于:

class BaseSystem
{
public:
   virtual void doThing() = 0;
}

我希望能够对类的所有派生类型进行标记,以便在应用程序启动时找到它们。在c#中,我会通过反射来寻找属性,或者只是寻找从基类派生的任何东西。

有没有类似的方法可以在c ++中做到这一点呢?我可以用某种东西制作类,并在编译时发现它们,并在向量中为它们创建一个实例?

编辑: 更多关于我要解决的问题的背景。

我正在创建一个静态库,使程序员可以实现实体组件系统模式来形成游戏引擎的基础。这个想法是库具有一个基类,他们可以从中实现系统,并且系统管理员将能够发现它们,然后在游戏开始时运行它们。

1 个答案:

答案 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::_instancesderived1::_index之前,因此在调用derived_registry::register_derived_class之前被初始化。

您可以在wandbox上看到这种方法的作用。

使其变得愚蠢

尽管上面的实现可行,但是它很麻烦,而且仍然有可能有人添加了新的派生类,却忘记了注册它。

要简化注册部分,您可以使用this question的答案中所述的CRTP模式,StoryTeller在评论中链接到您的问题。

尽管这可以使注册非常容易,但是只有当每个派生类从CRTP基继承或实现注册本身时,注册表仍然可以正常工作,这很容易忘记。为了确保别无选择,只能继承自CRTP基类,您还可以将base的构造方法设为私有,并使CRTP基类成为唯一的朋友。这样一来,如果不注册新的派生类,就不会偶然地直接从base继承。