【问题标题】:How can one design a base class, so it knows about all the "derived" classes, at run time?如何设计一个基类,使其在运行时了解所有“派生”类?
【发布时间】:2016-06-10 11:16:48
【问题描述】:

通常,如果您事先知道要创建的所有类型,则可以执行以下操作:

typedef enum{
    BASE_CREATURE_TYPE = 0,
    ANIMAL_CREATURE_TYPE,
    ...
}CREATURE_TYPES

但这变得乏味,因为每次创建新类时,都需要更新枚举。此外,CREATURE_TYPES 仍然只是枚举中的项目 - 如何绑定到实际类?

我想知道是否有某种方法,我可以只编写类,然后在运行时,无需实际实例化对象,创建一个包含所有类型的集合。

这在 C++ 中可行吗?在 Java 中,有一种叫做“静态块”的东西,它在类被 JVM 加载时执行。

编辑:这个问题与静态块无关 - 这只是一个示例 - 我想知道是否有某种方法可以执行方法或代码块,以便我知道运行时存在哪些类,而无需实际创建一个对象

编辑:我的意思是所有类型的集合,而不是“地图”,因此我可以创建每种类型的对象,而无需维护列表。

编辑:我想要这个的原因是因为我正在尝试创建一个函数,该函数可以调用作为应用程序一部分的所有派生类的方法。例如,假设我有几个类都派生自类 Foo,并且有一个 balls():

Foo{
   balls();
}

Boo : public Foo{
   balls();
}

Coo: public Foo{
   balls():
}

在运行时,我想知道所有派生类,以便调用:

DerivedClass:balls();

编辑:请注意,我不需要知道每个派生类的所有成员,我只想知道所有派生类是什么,所以我可以在每个派生类上调用 balls()。

编辑:这个问题类似:How to automatically register a class on creation

但不幸的是,他正在存储一个 std::string()。如何引用实际的类?

编辑:在下面 Smeehey 的回答中,在 main 方法中,我将如何实际创建每个类的实例,并同时调用静态和非静态方法?

【问题讨论】:

  • “创建地图”?什么样的地图?卢克天行者的地图?
  • 你能解释一下你为什么想要这个吗?这样回答可能会更容易。
  • @ShayNehmad 我想要它,以便我可以动态填充一个菜单,该菜单用于创建各种派生类型。但是将一个按钮绑定到每个派生类型会非常乏味,所以我试图确定是否有任何方法可以“反映”作为应用程序一部分的所有派生类,以便我可以动态调用所有方法,而无需了解每种类型。
  • 我认为可以使用预处理器命令甚至专用脚本。或外部 DLL,如插件子系统。我应该详细说明还是这不是您要找的?
  • @ShayNehmad - 我想避免编写构建脚本,因为这违背了我想要做的事情的目的。你能解释一下我如何使用预处理器命令来做到这一点吗?

标签: c++


【解决方案1】:

您可以为所有类创建一个静态注册表,并使用几个辅助宏在其中注册新类型。下面是一个基本的工作演示,它从 Base 创建了 2 个派生类。要添加新类,您只需使用显示的两个宏 - 一个在类内部,一个在类外部。注意:该示例非常简单,不涉及检查重复项或其他错误条件以最大限度地提高清晰度。

class BaseClass
{
};

class Registry
{
public:
    static void registerClass(const std::string& name, BaseClass* prototype)
    {
        registry[name] = prototype;    
    }

    static const std::map<std::string, BaseClass*>& getRegistry() { return registry; };

private:
    static std::map<std::string, BaseClass*> registry;
};

std::map<std::string, BaseClass*> Registry::registry;

#define REGISTER_CLASS(ClassType) static int initProtoType() { static ClassType proto; Registry::registerClass(std::string(#ClassType), &proto); return 0; } static const int regToken;
#define DEFINE_REG_CLASS(ClassType) const int ClassType::regToken = ClassType::initProtoType(); 

class Instance : public BaseClass
{
    REGISTER_CLASS(Instance)
};

DEFINE_REG_CLASS(Instance)

class OtherInstance : public BaseClass
{
    REGISTER_CLASS(OtherInstance)
};

DEFINE_REG_CLASS(OtherInstance)

int main()
{
    for(auto entry : Registry::getRegistry())
    {
        std::cout << entry.first << std::endl;
    }
    return 0;
}

上面注册了派生类的原型,例如可以用于复制构造其他实例。作为替代方案,应 OP 的要求,您可以拥有一个注册工厂方法而不是原型的系统。这允许您使用具有任何特定签名的构造函数而不是复制构造函数来创建实例:

class BaseClass
{
};

class Registry
{
public:
    using factoryMethod = BaseClass* (*)(int a, int b, int c);

    static void registerClass(const std::string& name, factoryMethod meth)
    {
        registry[name] = meth;    
    }

    static BaseClass* createInstance(const std::string& type, int a, int b, int c)
    {
        return registry[type](a, b, c);
    }

    static const std::map<std::string, factoryMethod>& getRegistry() { return registry; };

private:
    static std::map<std::string, factoryMethod> registry;
};

std::map<std::string, Registry::factoryMethod> Registry::registry;

#define REGISTER_CLASS(ClassType) static BaseClass* createInstance(int a, int b, int c)     \
                                  {                                                         \
                                      return new ClassType(a,b,c);                          \
                                  }                                                         \
                                  static int initRegistry()                                 \
                                  {                                                         \
                                       Registry::registerClass(                             \
                                           std::string(#ClassType),                         \
                                           ClassType::createInstance);                      \
                                       return 0;                                            \
                                  }                                                         \
                                  static const int regToken;                                \

#define DEFINE_REG_CLASS(ClassType) const int ClassType::regToken = ClassType::initRegistry(); 

class Instance : public BaseClass
{
    Instance(int a, int b, int c){}

    REGISTER_CLASS(Instance)
};

DEFINE_REG_CLASS(Instance)

class OtherInstance : public BaseClass
{
    OtherInstance(int a, int b, int c){}

    REGISTER_CLASS(OtherInstance)
};

DEFINE_REG_CLASS(OtherInstance)

int main()
{
    std::vector<BaseClass*> objects;
    for(auto entry : Registry::getRegistry())
    {
        std::cout << entry.first << std::endl;
        objects.push_back(Registry::createInstance(entry.first, 1, 2, 3));
    }
    return 0;
}

【讨论】:

  • 我仍在努力解决这个问题:D。对于一个菜鸟来说,它需要接受很多
  • 这与您所指的Java中的“静态块”非常相似。有很多样板,但基本上归结为为每个类创建一个静态实例,并让这个静态实例在注册表类中注册自己。到main 运行时,所有静态实例都已初始化并注册
  • 这只适用于 VC++ 吗?
  • 但在 Xcode 中 ClassType 给出一个错误为“未知类型名称”它是标准的一部分吗?
  • 你的意思是 Xcode IDE 吗? IDE 错误不是编译器错误,如果尚未编译,则需要编译它。看我上面的现场演示
【解决方案2】:

使用CRTP 设计与共同“祖先”的接口:

#include <vector>
#include <iostream>

/* Base */
struct IBase
{
    virtual void balls() = 0;
    virtual IBase *clone() const = 0;

private:
    static std::vector<IBase const *> _Derived;

public:
    static void
    create_all(void)
    {
    std::cout << "size: " << _Derived.size() << "\n";
        for (IBase const *a : _Derived)
        {
            IBase *new_object(a->clone());
            (void)new_object; // do something with it
        }
    }
};

std::vector<IBase const *> IBase::_Derived;

/* Template for CRTP */
template<class DERIVED>
class Base : public IBase
{
    static bool       created;
    static Base const *_model;

public:
    Base(void)
    {
        if (not created)
        {
            _Derived.push_back(this);
            created = true;
        }
    }
};

template<class DERIVED>
bool Base<DERIVED>::created = false;
template<class DERIVED>
Base<DERIVED> const *Base<DERIVED>::_model = new DERIVED;

/* Specialized classes */
struct Foo1 : public Base<Foo1>
{
    IBase *clone() const
    {
        std::cout << "new Foo1\n";
        return new Foo1(*this);
    }
    void balls() {}
};


struct Foo2 : public Base<Foo2>
{
    IBase *clone() const
    {
        std::cout << "new Foo2\n";
        return new Foo2(*this);
    }
    void balls() {}
};


int main(void)
{
    Foo1    a;
    IBase::create_all();
}

我尝试了这个解决方案,但我不知道为什么在运行程序时没有创建static Base const *_model;

【讨论】:

  • 那是多个基类。
  • CRTP 似乎在正确的轨道上。但是我将如何存储每个派生类的集合,所以我可以做类似 DerivedClass::balls(); ?
  • @John IBase *s 在IBase 中的静态向量,用于存储每个创建的类?
  • 我不明白。你能解释一下为什么这可以解决 OP 的问题吗?
  • @Boiethios 我对 C++ 不是很有经验,所以我无法弄清楚。我仍在尝试了解 CRTP 的工作原理。你能解释一下你将如何使用静态向量吗?
【解决方案3】:

您可以使用一个全局工厂来保存能够创建派生类的对象(unique_ptr's)的函数:

#include <memory>
#include <unordered_map>
#include <typeinfo>
#include <typeindex>

// Factory
// =======

template <typename Base>
class Factory
{
    public:
    template <typename Derived>
    struct Initializer {
        Initializer() {
            Factory::instance().register_producer<Derived>();
        }
    };
    typedef std::function<std::unique_ptr<Base>()> producer_function;
    typedef std::unordered_map<std::type_index, producer_function> producer_functions;

    static Factory& instance();

    void register_producer(const std::type_info& type, producer_function producer) {
        m_producers[std::type_index(type)] = std::move(producer);
    }

    template <typename Derived>
    void register_producer() {
        register_producer(
            typeid(Derived),
            [] () { return std::make_unique<Derived>(); });
    }

    producer_function producer(const std::type_info& type) const {
        auto kv = m_producers.find(std::type_index(type));
        if(kv != m_producers.end())
            return kv->second;
        return producer_function();
    }

    const producer_functions producers() const { return m_producers; }

    private:
    producer_functions m_producers;
};

template <typename Base>
Factory<Base>& Factory<Base>::instance() {
    static Factory result;
    return result;
}

// Test
// ====

#include <iostream>

class Base
{
    public:
    ~Base() {}
    virtual void print() = 0;
};

class A : public Base
{
    public:
    void print() override { std::cout << "A\n"; }
    static void f() {}
};
Factory<Base>::Initializer<A>  A_initializer;

class B : public Base
{
    public:
    void print() override { std::cout << "B\n"; }
};
Factory<Base>::Initializer<B>  B_initializer;

class C {};

int main()
{
    auto& factory = Factory<Base>::instance();

    // unique_ptr
    auto producerA = factory.producer(typeid(A));
    if(producerA) {
        auto ptrA = producerA();
        ptrA->print();
    }

    // shared_ptr
    auto producerB = factory.producer(typeid(B));
    if(producerB) {
        std::shared_ptr<Base> ptrB(producerB());
        ptrB->print();
    }

    // missing
    auto producerC = factory.producer(typeid(C));
    if( ! producerC) {
        std::cout << "No producer for C\n";
    }

    // unordered
    for(const auto& kv : factory.producers()) {
        kv.second()->print();
    }
}

注意:工厂不提供在没有对象的情况下调用静态成员函数的方法。

【讨论】:

    猜你喜欢
    • 2023-03-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-05
    • 2012-03-08
    • 1970-01-01
    • 2011-11-09
    相关资源
    最近更新 更多