正如其他人在 cmets 中对您的问题所指出的那样,通常不可能在编译时使用 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继承。