如何设计一个基类,在运行时知道所有“派生”类?(涉及IT技术)

9

通常情况下,如果您事先知道要创建的所有类型,可以像这样操作:

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()方法。

编辑说明:这个问题与如何在创建时自动注册类相似。

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

编辑说明:在Smeehey的下面的答案中,如何实际创建每个类的一个实例,并调用静态和非静态方法?


2
"创建一个地图"? 是什么类型的地图?是指到卢克·天行者的地图吗? - Kerrek SB
@ShayNehmad 我想实现动态填充菜单,用于创建不同的派生类型。但是将每个派生类型都与按钮绑定会非常繁琐,因此我正在尝试确定是否有任何方法可以"反射"应用程序中的所有派生类,以便我可以动态调用所有方法,而不必知道每种类型的具体信息。 - Rahul Iyer
一个类可以向一个类工厂注册自己。这个注册可以由一个静态对象的构造函数来实现。你需要为每个派生类声明一个静态对象,或者使用一些模板魔术(CRTP)自动声明一个。 - n. m.
@JerryJeremiah,我不仅想调用静态函数,还想能够实例化对象。我上面链接的CRTP问题非常接近我想要的,但我没有办法引用“类型”,类似于在objective-c中所做的那样,以创建一个不知道类型的对象。 - Rahul Iyer
刚刚说个话题:在[tag:c++]中不要使用typedef来定义structclassenumunion,这是一种不必要的负担 - PaperBirdMaster
显示剩余8条评论
3个回答

4

您可以创建一个静态注册表来管理所有类,并使用一些帮助宏在其中注册新类型。下面是一个基本的工作演示,它创建了2个从Base派生的类。要添加新类,只需使用两个宏 - 一个在类内部,一个在类外部。注意:此示例非常简单,不关心诸如检查重复项或其他错误条件等事项,以最大化清晰度。

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;
}

这与您所提到的Java中的“静态块”非常相似。有很多样板文件,但基本上归结为创建每个类的静态实例,并使此静态实例在注册表类中注册自身。当main运行时,所有静态实例都将被初始化并注册。 - Smeeheey
我是说,你在编译时实际上是否遇到了这个错误,还是只是IDE在编辑过程中向你突出显示的东西?如果这是一个编译器错误,并且你确实按照上面的代码使用它,那么你可能有一个不符合规范的编译器。如果是这样,请分享完整的错误信息。 - Smeeheey
抱歉,那个问题是针对@John的。 - Smeeheey
我不明白 - 通常我会这样创建一个实例:Foo foo = new Foo(a,b);。但在你的情况下,你说你已经创建了一个实例。那么如果我有更复杂的构造函数怎么办?如果我想创建同一类型的多个实例怎么办?如果我只知道每个派生类都有一个需要3个参数的构造函数,我该如何引用该类型并执行类似DerivedClass boo = new DerivedClass(a,b,c)的操作? - Rahul Iyer
@Smeeheey 谢谢你的回答 - 我非常感激!希望我没有听起来很生硬,只是想了解它是如何工作的。令人惊叹.. - Rahul Iyer
显示剩余15条评论

1
使用具有公共“祖先”接口的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; 没有被创建。

2
有多个基类。 - user2249683
2
我不理解。你能解释一下为什么这可以解决 OP 的问题吗? - Shay Nehmad
@Boiethios,我在C++方面经验不是很丰富,所以无法弄清楚这个问题。我仍在努力理解CRTP的工作原理。你能解释一下如何使用静态向量吗? - Rahul Iyer
@John,我相信你可以用那种方式解决问题,让我一些时间来编写完整的代码。 - Boiethios
@Boiethios 谢谢 - 我有一种感觉,你和Smeeheey的答案都会非常有趣。 - Rahul Iyer
显示剩余3条评论

1
你可以使用一个全局工厂,其中包含能够创建派生类对象(unique_ptr)的函数:
#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();
    }
}

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接