是否有可能知道所有派生类的名称?

4
假设我们有一个基类和一系列派生类。有没有任何方法或机制可以通过编程知道所有派生类的名称?
也许反射是一个好主意,但在C++中不可用。我想可能会有某种模板可以在编译期间完成这项工作。
class Base{
public:
    virtual void print(){
        // This function should print all the names of derived class.
    }
    virtual Base* getInstance(string class_name){
        // This function should return an instance related to the class name.
    }
};

class Derived_1 : public Base{ // Suppose we have 100 Derived_X classes, 
                 // so we don't want to add its name to a list manually.
};

int main(){
    Base base;
    base.print(); // This should print the name of all the derived class.
    base.getInstance("Derived_1"); // This should return an instance of Derived_1
    return 0;
}

1
不,使用其他语言来完成这项工作。 - Tatsuyuki Ishi
即使 C++ 有反射,基类如何可能知道所有派生类呢? - 463035818_is_not_a_number
1
顺便提一下,每个类都可以向其基类注册(也许使用CRTP)。 - Jarod42
1
听起来像是 XY 问题。你真正想要达到什么目的?工厂模式是否有助于你的事业? - Aziuth
1
@Aziuth 不需要。工厂模式需要我们手动将所需的类添加到工厂中。如果我有大量需要添加到工厂的类,那么这将是浪费时间的。我想要的是让工厂自动查找所需的类(例如,继承自基类的类)。 - Peter YANG
显示剩余3条评论
3个回答

2

这个解决方案基于您似乎正在寻找一个工厂的事实。它使用一个小型宏来简化类注册,希望您不介意。

factory.h

#ifndef __FACTORY_H__
#define __FACTORY_H__

#include <map>
#include <functional>
#include <string>
#include <iostream>

template<class B>
class Factory {
  std::map<std::string, std::function<B*()>> s_creators;

public:
  static Factory<B>& getInstance() {
    static Factory<B> s_instance;
    return s_instance;
  }

  template<class T>
  void registerClass(const std::string& name) {
    s_creators.insert({name, []() -> B* { return new T(); }});
  }

  B* create(const std::string& name) {
    const auto it = s_creators.find(name);
    if (it == s_creators.end()) return nullptr; // not a derived class
    return (it->second)();
  }

  void printRegisteredClasses() {
    for (const auto &creator : s_creators) {
      std::cout << creator.first << '\n';
    }
  }
};
#define FACTORY(Class) Factory<Class>::getInstance()

template<class B, class T>
class Creator {
public:
  explicit Creator(const std::string& name) {
    FACTORY(B).registerClass<T>(name);
  }
};

#define REGISTER(base_class, derived_class) \
  Creator<base_class, derived_class> s_##derived_class##Creator(#derived_class);

#endif

example.cpp

#include "factory.h"
#include <memory>

class Base {
public:
  virtual void printName() const { std::cout << "Base\n"; }
};

class Derived1 : public Base {
public:
  virtual void printName() const override { std::cout << "Derived1\n"; }
};
REGISTER(Base, Derived1);

class Derived2 : public Base {
public:
  virtual void printName() const override { std::cout << "Derived2\n"; }
};
REGISTER(Base, Derived2);

int main() {
  std::cout << "Registered classes:" << std::endl;
  FACTORY(Base).printRegisteredClasses();

  std::cout << "---" << std::endl;
  std::unique_ptr<Base> derived1(FACTORY(Base).create("Derived1"));
  derived1->printName();

  return 0;
}

注意:需要使用C++11。


我喜欢这种方法!但是,在注册类时我遇到了“访问冲突,无法读取位置”的异常。 - ThunderWiring
@ThunderWiring 我的代码在派生类使用 s_creators 映射时,存在初始化顺序问题。我已将解决方案更改为单例模式,以确保在第一次使用映射之前创建它。感谢您指出这一点。 - cbuchart

1
对于getInstance,你可以将其声明为模板(需要C++14)。要获取程序中所有派生类的名称,可能需要使用一些预处理器技巧。
#include <type_traits>

class Base
{
public:
    virtual ~Base () = default;

  template < typename T,
             typename = std::enable_if_t<std::is_base_of<Base, T>::value, void>
             >
    T getInstance() { return T{}; }
};

class Derived : public Base {};
class NotDerived {};


int main(){
    Base base;

    base.getInstance<Derived>();

    // error: no matching member function for call to 'getInstance'
    //base.getInstance<NotDerived>();
}

动态转换的意义何在,当方法返回一个 Base* 时? - 463035818_is_not_a_number
@tobi303 确实如此。它应该返回 T*(否则整个方法就没有意义)。 - Henri Menke
@tobi303 哦,我明白了。那么,我想 SFINAE 解决方案更合适。 - Henri Menke
1
@tobi303 这是不可能的。类型必须在编译时知道。 - Henri Menke
1
@HenriMenke “不可能”这个说法有点过了……任何带有运行时类型系统(解释器、数据库等)的程序都需要一种方法来将函数调用分派到在编译时已知的一组函数,这些函数是从用户提供的规范中得出的,而这些规范在编译时是未知的。你需要一种从字符串到可调用对象的映射,它返回Base*,因此显然对于系统中的每个派生类型,你都需要记住向该映射添加所需的分派信息。 - sigbjornlo
显示剩余4条评论

0
关于派生类的名称,我提出了一个基于BaseList类/结构的解决方案,其中包含一个静态的std::set(或其他容器)名称集合,一个模板Base类/结构,它继承自BaseList,其模板参数是派生类(CRTP风格),并且(为了简化派生类/结构的构造,使用C风格的宏(我知道...宏是魔鬼...但有时候...)来创建派生类/结构的声明,其中包含必要的静态方法,声明派生类/结构的名称和一个成员(激活名称的注册)。

以下是一个完整的示例(不幸的是,这是一个C++11示例)

#include <set>
#include <string>
#include <iostream>

struct BaseList
 {
   static std::set<std::string> const & derList (std::string const & dn)
    {
      static std::set<std::string> dl;

      if ( dn.size() )
         dl.insert(dn);

      return dl;
    }

   static void print ()
    {
      std::cout << "derived names: ";
      for ( auto const & dn : derList("") )
         std::cout << dn << ", ";
      std::cout << std::endl;
    }
 };

template <typename Der>
struct Base : public BaseList
 {
   static std::size_t setNameInList () 
    { return derList(Der::name()).size(); }

   static std::size_t id;
 };

template <typename Der>
std::size_t Base<Der>::id = setNameInList();

#define setDerived(nameDer) \
struct nameDer : public Base<nameDer>\
 { \
   std::size_t idc { id }; \
   static std::string name () \
    { return #nameDer; }

setDerived(Derived_1)
   // other elements
 };

setDerived(Derived_2)
   // other elements
 };

setDerived(Derived_3)
   // other elements
 };

int main()
 {
   BaseList::print();
 }

关于getInstance()问题,我能想到的唯一解决方案是Enry Menke提出的相同解决方案(+1),因此我建议您通过模板类型参数获取实例。

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