【问题标题】:Generic Aggregation of C++ Objects by Attribute When Attribute Name is Unknown at Runtime当属性名称在运行时未知时,按属性对 C++ 对象进行通用聚合
【发布时间】:2011-02-17 09:14:00
【问题描述】:

我目前正在实现一个系统,其中包含许多类的代表对象,例如客户端、业务、产品等。标准业务逻辑。正如人们所期望的那样,每个类都有许多标准属性。

我有一长串基本相同的要求,例如:

  • 能够检索所有行业为制造业的企业。
  • 能够检索所有位于伦敦的客户

类业务有属性sector,客户有属性location。显然这是一个关系问题,在伪 SQL 中看起来像:

SELECT ALL business in business' WHERE sector == manufacturing

不幸的是,插入数据库不是一种选择。

我想要做的是有一个单一的通用聚合函数,其签名将采用以下形式:

vector<generic> genericAggregation(class, attribute, value);

其中 class 是我要聚合的对象的类,attribute 和 value 是感兴趣的类属性和值。在我的示例中,我将向量作为返回类型,但这不起作用。可能更好地声明相关类类型的向量并将其作为参数传递。但这不是主要问题。

如何接受类、属性和值的字符串形式的参数,然后将它们映射到通用对象聚合函数中?

由于不发布代码是不礼貌的,下面是一个虚拟程序,它创建了一堆富有想象力的类的对象。包括一个特定的聚合函数,它返回 B 对象的向量,其 A 对象等于在命令行中指定的 id,例如..

$ ./aggregations 5

它返回所有A对象'i'属性等于5的B。见下文:

#include <iostream>
#include <cstring>
#include <sstream>
#include <vector>

using namespace std;

//First imaginativly names dummy class
class A {
 private:
  int i;
  double d;
  string s; 
 public:
  A(){}
  A(int i, double d, string s) {
   this->i = i;
   this->d = d;
   this->s = s;
  }
  ~A(){}
  int getInt() {return i;} 
  double getDouble() {return d;} 
  string getString() {return s;}
};

//second imaginativly named dummy class
class B {
 private:
  int i;
  double d;
  string s;
  A *a; 
 public:
  B(int i, double d, string s, A *a) {
   this->i = i;
   this->d = d;
   this->s = s;
   this->a = a;
  }
  ~B(){}
  int getInt() {return i;} 
  double getDouble() {return d;} 
  string getString() {return s;}
  A* getA() {return a;}
};

//Containers for dummy class objects
vector<A> a_vec (10);
vector<B> b_vec;//100

//Util function, not important..
string int2string(int number) {
 stringstream ss;
 ss << number;
 return ss.str();
}


//Example function that returns a new vector containing on B objects
//whose A object i attribute is equal to 'id'
vector<B> getBbyA(int id) {
 vector<B> result;
 for(int i = 0; i < b_vec.size(); i++) {
  if(b_vec.at(i).getA()->getInt() == id) {
   result.push_back(b_vec.at(i));
  }
 }
 return result;
}



int main(int argc, char** argv) {

 //Create some A's and B's, each B has an A...
 //Each of the 10 A's are associated with 10 B's.
 for(int i = 0; i < 10; ++i) {
  A a(i, (double)i, int2string(i));
  a_vec.at(i) = a;
  for(int j = 0; j < 10; j++) {
   B b((i * 10) + j, (double)j, int2string(i), &a_vec.at(i));   
   b_vec.push_back(b);
  }
 }

 //Got some objects so lets do some aggregation

 //Call example aggregation function to return all B objects 
 //whose A object has i attribute equal to argv[1]
 vector<B> result = getBbyA(atoi(argv[1]));

 //If some B's were found print them, else don't...
 if(result.size() != 0) {
  for(int i = 0; i < result.size(); i++) {
   cout << result.at(i).getInt() << " " << result.at(i).getA()->getInt() << endl;
  }
 }
 else {
  cout << "No B's had A's with attribute i equal to " << argv[1] << endl;
 }

 return 0;
}

编译:

g++ -o aggregations aggregations.cpp

如果你愿意:)

而不是实现一个单独的聚合函数(即示例中的 getBbyA()),我希望有一个通用聚合函数,它考虑所有可能的类属性对,以便满足所有聚合要求......并且在事件附加属性稍后添加,或附加聚合要求,这些将自动考虑。

所以这里有一些问题,但我正在寻求深入了解的主要问题是如何将运行时参数映射到类属性。

我希望我已经提供了足够的细节来充分描述我正在尝试做的事情......

【问题讨论】:

  • 你能修改类吗? (如果你不能,你会遇到麻烦。)
  • @Beta:不一定,也可以考虑为这些访问定义 Function 对象。有很多可能性。
  • @Matthieu M.:我不明白。我只能想到两种方法来做到这一点,而无需为每个类的每个字段编写单独的例程:1)修改类或 2)使用宏 [不寒而栗]。我很乐意学习另一种方法......
  • 我们的想法是为每个类(示例)注册一个对象,它将类作为参数,然后有一个方法 getattr(name) 将字段的值作为字符串返回(或如果不存在此类字段,则为 Null/Void 值)。它确实需要为每个班级做额外的工作,但来自外部。从封装的角度来看更好,因为您只能访问公共字段。

标签: c++ class mapping attributes aggregation


【解决方案1】:

您必须将数据存储在哈希映射或法线映射中,而不是作为原始 C++ 数据。如果不对 C++ 中的每个可能值进行 switch/case/default,就不可能从字符串 "hai" 转到 A.hai。

至于前面的回答,这是反射,不是RTTI。 C++ 确实有 RTTI - 这就是 typeid() 的用途。但是,C++ 绝对不支持它,随便你怎么称呼它。

【讨论】:

    【解决方案2】:

    你确实提供了足够的细节 :) 我希望你明白这并不容易。

    您正在寻找的东西称为“反射”,即对象描述自身的能力。

    反思很困难,但还是可以做到的!

    我通常是元模板之类的粉丝,但这次我将提出一个纯粹的 OO-Way。从某种意义上说,它是内在的,您必须实际修改您的对象以支持这一点。

    基本想法是按照支持该习语的语言本身的方式进行。

    1- 我们调整基本类型

    enum Type
    {
      Int,
      String
    };
    
    class Field
    {
    public:
      Type type() const { return mType; }
    
      virtual Field* clone() const = 0;
    
      virtual std::string toString() const = 0;
      virtual void fromString(const std::string& s) = 0;
    
      virtual ~Field() {}
    protected:
      explicit Field(Type t): mType(t) {}
    private:
      Type mType;
    };
    
    bool operator==(const Field& lhs, const Field& rhs)
    {
      return lhs.type() == rhs.type() && lhs.toString() == rhs.toString();
    }
    
    class IntField: public Field
    {
    public:
      typedef int data_type;
    
      IntField(): Field(Int), mData() {}
    
      virtual IntField* clone() const { return new IntField(*this); }
    
      data_type get() const { return mData; }
      void set(data_type d) { mData = d; }
    
      virtual std::string toString() const
      { return boost::lexical_cast<std::string>(mData); }
    
      virtual void fromString(const std::string& s)
      { mData = boost::lexical_cast<data_type>(s); }
    
    private:
      data_type mData;
    };
    
    // Type information allow for more efficient information
    bool operator==(const IntField& lhs, const IntField& rhs)
    {
      return lhs.get() == rhs.get();
    }
    

    2- 然后我们构建一个能够容纳所有这些的类

    class Object
    {
    public:
      virtual ~Object(); // deal with memory
    
      Field* accessAttribute(const std::string& name);
      const Field* getAttribute(const std::string& name) const;
      void setAttribute(const std::string& name, const Field& value);
    
    protected:
      // Deal with memory
      Object();
      Object(const Object& rhs);
      Object& operator=(const Object& rhs);
    
      void addAttribute(const std::string& name, const Field& value);
    
    private:
      std::map<std::string, Field*> mFields; // tricky, we must deal with memory here
    };
    

    3- 用法:

    class Person: public Object
    {
    public:
      Person(const std::string& surname,
             const std::string& givenname,
             int age): Object()
      {
        this->addAttribute("surname", StringField(surname));
        this->addAttribute("givenname", StringField(givenname));
        this->addAttribute("age", IntField(age));
      }
    };
    
    int main(int argc, char* argv[])
    {
      // initialize
      std::vector<Person> persons = /**/;
    
      std::cout << "Please enter an attribute and the expected value" << std::endl;
      std::string name, value;
      std::cin >> name >> value;
    
      std::vector<Person*> result;
      for (std::vector<Person>::iterator it = persons.begin(), end = persons.end();
           it != end; ++it)
      {
        const Field* field = it->getAttribute(name);
        if (field && field->toString() == value) result.push_back(&(*it));
      }
    
      std::cout << "Selected persons for " << name
                << " = " << value << " are:\n";
      for (std::vector<Person*>::iterator it = result.begin(), end = result.end();
           it != end; ++it)
      {
        const Person& person = **it;
        std::cout << "  " << person.surname() << " " << person.givenname() << "\n";
      }
      std::cout.flush();
    }
    

    可能还有许多其他方式,或多或少是自动化的。值得注意的是,人们可以考虑现有类的适应结构。类似于宏 BOOST_FUSION_ADAPT_STRUCT 的东西,但具有给定结构的名称的附加值。

    我担心它可能会变得更加晦涩难懂,但没有明显的收获......另外,我应该补充一点,如果可能的话,您可能愿意直接使用原生支持此功能的语言进行编码 :)

    【讨论】:

      【解决方案3】:

      因此,如何将运行时参数映射到类属性这个主要问题的答案是你不能

      您要查找的通常称为运行时类型信息(简称RTTI),即在运行时检索有关类型的信息的能力/em>。

      C++ 不支持 RTTI。不管是好是坏,故事都没有结束。

      因此,您必须自己发明一些东西。概括地说,您将使所有B 类实现一个接口,该接口将具有类似getClass( string className ) 的方法,该方法将返回指向您的A 对象的指针,该对象又将实现另一个接口,该接口将具有像getAttribute( string attrName ) 这样的方法,然后您就可以将它与您的值进行比较。另一种可能性是A 使用方法compareToValue( string attrName, string value ) 而不是getAttribute,这样它就可以处理值的机制,而不仅仅是作为字符串来处理。

      此外,如果需要,您可以使用相应的 getAllClassesgetAllAttributes 方法来检索列表。

      此外,如果您未绑定到 C++,还有其他支持 RTTI 的平台。 Java 和 .NET 就是两个例子。

      【讨论】:

        猜你喜欢
        • 2010-11-24
        • 2015-11-29
        • 1970-01-01
        • 2015-10-27
        • 2022-12-04
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多