C++ 编译时的派生类发现

C++ 编译时的派生类发现,c++,c++17,C++,C++17,我有一个基类,我的许多类都是从该基类派生的,例如: class BaseSystem { public: virtual void doThing() = 0; } 我希望能够标记类的所有派生类型,以便在应用程序启动时找到它们。在c#中,我会通过反射来实现这一点,或者查找属性,或者只查找从基类派生的任何内容 有类似的方法,我可以在C++中用这些方法来生成类,并在编译时发现它们,并在向量中为它们创建一个实例吗?p> 编辑: 更多的是我试图解决的问题的背景 我正在创建一个静态库,允许程序员实

我有一个基类,我的许多类都是从该基类派生的,例如:

class BaseSystem
{
public:
   virtual void doThing() = 0;
}
我希望能够标记类的所有派生类型,以便在应用程序启动时找到它们。在c#中,我会通过反射来实现这一点,或者查找属性,或者只查找从基类派生的任何内容

有类似的方法,我可以在C++中用这些方法来生成类,并在编译时发现它们,并在向量中为它们创建一个实例吗?p> 编辑: 更多的是我试图解决的问题的背景


我正在创建一个静态库,允许程序员实现实体组件系统模式,以形成游戏引擎的基本框架。这个想法是库有一个基类,它们可以从中实现系统,系统管理员可以发现它们,然后在游戏开始时运行它们。

正如其他人在对您的问题的评论中指出的那样,一般来说,在编译时,不可能用C++来检测特定基类的所有现有派生类。p> 但是,如果您只需要一种机制来避免让一个地方知道所有现有的派生类,那么您可以做一些事情,尽管不是在编译时

基本思想 其思想是使用静态成员变量的初始化(保证在执行
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;
这也说明了为什么将
derived_注册表::_instances
向量定义为
inline
变量很重要:我们需要确保
derived_注册表::register_derived_类
仅在
derived_注册表::_instances
已初始化后调用。最简单的方法是使用这样一个事实,即当在一个转换单元中定义了多个具有静态存储持续时间的变量时,保证按照定义它们的顺序初始化它们。由于我们在头文件中定义了
derived\u registry:::\u实例
,我们保证
derived\u registry::\u实例
derived1::\u index
之前初始化,因此在调用
derived\u registry::register\u derived\u class
之前初始化

您可以在上看到这种方法的实际应用

让它成为傻瓜 虽然上面的实现可以工作,但它相当麻烦,而且仍然有可能有人添加了一个新的派生类,但忘记了注册它

为了简化注册部分,您可以使用CRTP模式,如对您的问题的注释中链接的问题的回答中所述


虽然这可以使注册变得非常容易,但只有当每个派生类从CRTP基继承或实现注册本身时,注册表才能正常工作,这很容易忘记。为了确保除了从CRTP基类继承之外别无选择,您还可以将
base
的构造函数设为私有,并将CRTP基类设为唯一的友元。那么,如果不注册新的派生类,就不能意外地直接从
base
继承。

那么您想要一个
std::vector
,每个派生类都有一个对象?为什么?这听起来像个XY问题。如果你有这样一个实例向量,你会用它做什么?简单的答案是“不”。没有办法在C++中列出类或类成员。这基本上是反射的许多用途之一,C++没有。您必须手动列出这些类。也许可以帮助您..?根据melpomene的评论,这看起来像是一个XY问题-您想做X,认为Y将实现X,但您不知道如何做Y,所以询问如何做。XY问题对人们来说是令人沮丧的,因为Y通常是无法实现的,对不理解X的人来说也是毫无意义的。所以试着描述一下你想要解决的实际问题(即X)——可能有一些有用的解决方案不依赖于解决你问到的假问题(Y)。我不确定这是否真的是可移植的。静态成员“不保证在
main
之前进行初始化”。更确切地说,松散地说,它们只保证在使用odr的TU中的任何其他实体之前进行初始化,因此,如果您从未对所链接的某个模块具有代码依赖性,则不存在调用全局初始化器的标准要求。它在实践中可能会起作用,但我不认为您可以根据标准保证这一点。看。@KerrekSB你说得对,我错误地记得[basic.start.static]中给出的静态初始化保证是针对具有静态存储持续时间的各种变量的初始化。将
\u实例
标记为
内联
的基本原理是为了防止静态初始化顺序失败。@KerrekSB但我认为这可能仍然符合标准,因为幸运的是我们处理的派生类本身定义了虚拟成员函数的重写,如果我读得正确的话,在TU中定义的ODR已经被使用了。因此,TU中有另一个实体定义了静态成员变量
\u index
,它是ODR使用的,因此
\u index
被初始化。非常感谢您的详细解释。这就解决了我的问题:)
// 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";
}