从C++中的基类反映子类

Reflect child classes from base classes in C++

本文关键字:子类 基类 C++      更新时间:2023-10-16

我们希望C++中的解决方案必须能够执行以下操作:给定一个特定类型的字符串,比如说"a",我们希望找到从"a"派生的所有类型。实例化从"A"派生的类型中的新对象。例如,假设我们有一个类,VehicleEntity。VehicleEntity有子类PassangerCarEntity、TruckEntity、TrainEntity和BoatEntity。我们不确定可能有什么车辆实体,因为可以添加包含更多车辆实体的库。例如,可以在部署后添加源自VehicleEntity的飞机实体。在应用程序中,当用户想要选择VehicleEntity时,用户应该能够选择从Vehicle实体派生的任何实体。这包括PassangerCarEntity、TruckEntity、TrainEntity、BoatEntity和AirplaneEntity。用户选择一个实体,比如说飞机实体,必须实例化一个类型为飞机实体的新对象。

下面是C#中的一个概念示例,说明我们希望在C++中实现什么。在C#中,下拉列表的项目可以如下检索:

Type vehicleEntityType = typeof(VehicleEntity);
List<Type> types = new List<Type>();
foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
    types.AddRange(assembly.GetTypes().Where(x => vehicleEntityType.IsAssignableFrom(x) && !x.IsGenericType && !x.IsAbstract));
dropDownBox.ItemList = types;
//To instantiate the object:
List<VehicleEntity> vehicleList = new List<VehicleEntity>();
Type newVehicleType = (Type)dropDownBox.SelectedItem;
object newVehicle = Activator.CreateInstance(newVehicleType ); // default constructor used, parameterless constructors for vehicles.
vehicleList.Add(newVehicle);

标准C++似乎无法做到这一点,因为它不在对象上存储任何元数据。存在为C++提供反射的外部库。RTTI和boost。Mirror似乎无法做到这一点。我确信,我们并不是唯一一个遇到这个问题的人,而且存在解决方案。有什么解决方案可以解决我们的问题?外部图书馆或其他方式。

对我来说,使用反射来完成这样的任务似乎有点过于复杂。从另一个角度来看这个问题:你想知道,当有人从你的类派生时。这可以通过以下方式(但不限于)实现:

  • 创建静态VehicleRepository
  • 准备以下功能:

    static VehicleContext * VehicleRepository::Register(string newName, std::function<Vehicle * (void)> vehicleCtor)
    

    该方法的任务是登记可供建造的新型车辆。

  • 需要VehicleBase的基类ctor:中的VehicleContext

    VehicleBase::VehicleBase(VehicleContext * newContext)
    
  • 为派生类创建静态actor,从而获得上下文:

    static VehicleContext * context;
    static NewVehicle()
    {
        context = VehicleBase::Register("NewVehicle", []() { return new NewVehicle(); });
    }
    

    C++没有静态构造函数,但可以通过以下方式模拟它们:C++中的静态构造函数。此外,这些代码可以放在应用程序或库的某个初始化部分。

  • 实例化新派生类时使用上下文:

    NewVehicle::NewVehicle()
        : base(NewVehicle::context)
    {
    }
    

这是一种确保每辆车都能在VehicleDepository中正确注册的简单方法。您还将拥有所有注册车辆的集中存储库,以及创建这些车辆的适当构造函数。


通常,您必须创建一个体系结构,该体系结构遵循以下规则:

如果类在实例化之前没有在某个全局存储库中注册,那么实例化派生类应该是不可能的。

上面介绍的另一种方法:

BaseVehicle::BaseVehicle(Token token)
{
    // token is passed from the derived class
    if (!GlobalRepository.IsRegistered(token))
        throw new std::exception("Vehicles should be registered prior to instantiating!");
}

令牌可以是类名、与类相关联的GUID或其他东西。思考的食物:)

这不是您想要的,但您可以在C++中使用一个简单的基类。它确实使您能够检查基类。然而,它确实比C#中需要更仔细的考虑和规划——正如那些评论你帖子的人所建议的那样。。。

class Vehicle {
  public:
    virtual int move (void) = 0;
};
class Airplane : public Vehicle {
  public:
    int move (void);
    void autoPilot (void);
};
void GoFromMaineToSpainInVehicle (Vehicle *v);
int main (int argc, char *argv[]) {
    Vehicle *v = NULL;
    Airplane *cessna = new Airplane();
    // Request base class pointer
    v = dynamic_cast<Vehicle *>(cessna);
    if ( v ) { GoFromMaineToSpainInVehicle(v); }
    else { /*not a vehicle*/ }
    return 0;
}

如果我们想根据用户输入字符串获得一组对象。我们可以在这里实现的是使用字符串输入修改的Factory设计模式,我们可以决定返回哪种类型的车辆对象。

类似这样的东西:

#include <iostream>
#include <boost/any.hpp>
#include <boost/unordered_map.hpp>
#include<list>
using namespace std;
boost::unordered_map<string, boost::any> objects;
class A{
  public:
    virtual void print()
    {
      cout<<"A"<<endl;
    }
};

class B:public A{
  public:
    void print()
    {
      cout<<"B"<<endl;
    }
};

class C:public A{
  public:
    void print()
    {
      cout<<"C"<<endl;
    }
};

class D{
  public:
    virtual void print()
    {
      cout<<"D"<<endl;
    }
};
class E:public D{
  public:
    void print()
    {
      cout<<"E"<<endl;
    }
};

class F:public D{

  public:
    void print()
    {
      cout<<"F"<<endl;
    }
};
boost::unordered_map<string, list<boost::any> > createInstance(string input)
{
  boost::unordered_map<string, list<boost::any> > list;
  if(input.compare("A") == 0 )
  {
    list[input].push_back(new A);
    list[input].push_back(new B);
    list[input].push_back(new C);
  }
  else if(input.compare("D") == 0 )
  {
    list[input].push_back(new D);
    list[input].push_back(new E);
    list[input].push_back(new F);
  }
  return list;
}

int main()
{
  string input;
  cout<<"input "<<endl;
  cin>>input;
  boost::unordered_map<string, list<boost::any> > objectList = createInstance(input);
  for(boost::unordered_map<string, list<boost::any> >::iterator itr = objectList.begin();
      itr!= objectList.end(); ++itr)
  { 
    for(list<boost::any>::iterator itrList = itr->second.begin();
        itrList!= itr->second.end();++itrList)
    {
      if((*itrList).type() == typeid(A*))
      (boost::any_cast<A*>(*itrList))->print();
      else if((*itrList).type() == typeid(B*))
      (boost::any_cast<B*>(*itrList))->print();
      else if((*itrList).type() == typeid(C*))
      (boost::any_cast<C*>(*itrList))->print();
      else if((*itrList).type() == typeid(D*))
      (boost::any_cast<D*>(*itrList))->print();
      else if((*itrList).type() == typeid(E*))
      (boost::any_cast<E*>(*itrList))->print();
      else if((*itrList).type() == typeid(F*))
      (boost::any_cast<F*>(*itrList))->print();
    }
  }
  return 0;
}

您能提供更多关于如何扩展可用类型列表的详细信息吗?

正如其他评论者所说,你不能像在C++中描述的那样实现反射。

我假设所有的子类型都将在一些可重新分发的库(或不同的库)中定义,这些库将和应用程序动态链接。在这种情况下,我认为库应该负责提供可用类型的列表和一些工厂方法,以基于上下文实例化某些特定的子类型。

我找到了两种解决问题的方法。

第一个是通过反射库Reflex。它也被ROOT库使用(由欧洲核子研究中心实验室开发,并积极维护。)

第二个,似乎是@Spook和@Eugene试图达到的。Cplusplus

感谢所有的投入。