2010-05-09 12 views
1

Je suis actuellement en train de mettre en œuvre un système avec un certain nombre de classes représentant des objets tels que client, entreprise, produit, etc. Comme on pourrait s'y attendre, chaque classe a un certain nombre d'attributs standard.Agrégation générique d'objets C++ par attribut lorsque le nom d'attribut est inconnu au moment de l'exécution

J'ai une longue liste d'exigences essentiellement identiques tels que:

  • la possibilité de récupérer toutes les affaires dont l'industrie est la fabrication.
  • la possibilité de récupérer tous les clients basés à Londres

classe affaires a attribuer le secteur et le client a l'attribut emplacement. Il est clair que ce problème relationnel et pseudo SQL ressemblerait à quelque chose comme:

SELECT ALL business in business' WHERE sector == manufacturing 

Malheureusement brancher dans un DB n'est pas une option.

Ce que je veux faire est d'avoir une seule fonction d'agrégation générique dont la signature prendrait la forme:

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

Où est la classe d'objet que je veux agréger, attribut et valeur étant l'attribut de classe et valeur d'intérêt. Dans mon exemple, j'ai mis vector comme type de retour, mais cela ne marcherait pas. Probablement mieux de déclarer un vecteur de type de classe pertinent et de le passer en argument. Mais ce n'est pas le problème principal. Comment puis-je accepter les arguments sous forme de chaîne pour la classe, l'attribut et la valeur, puis les mapper dans une fonction d'agrégation d'objets générique?

Comme il est impoli de ne pas afficher de code, voici un programme factice qui crée un tas d'objets de classes imaginatives. Inclus est une fonction d'agrégation spécifique qui retourne un vecteur d'objets B dont l'objet A est égal à un identifiant spécifié sur la ligne de commande, par ex. ..

$ ./aggregations 5 

qui retourne tous les B dont les objets Un attribut 'i' est égal à 5. Voir ci-dessous:

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

Compile avec:

g++ -o aggregations aggregations.cpp 

Si vous souhaitez :) Au lieu d'implémenter une fonction d'agrégation séparée (c'est-à-dire getBbyA() dans l'exemple), je souhaiterais avoir une seule fonction d'agrégation générique. nction qui prend en compte toutes les paires d'attributs de classe possibles, de sorte que toutes les conditions d'agrégation sont respectées. Si des attributs supplémentaires sont ajoutés ultérieurement ou si des conditions d'agrégation supplémentaires sont requises, elles seront automatiquement prises en compte.

Il y a donc quelques problèmes ici, mais le principal que je cherche à comprendre est comment mapper un argument d'exécution à un attribut de classe.

J'espère avoir fourni suffisamment de détails pour décrire adéquatement ce que j'essaie de faire ...

+0

Pouvez-vous modifier les classes? (Vous allez avoir des problèmes si vous ne pouvez pas.) – Beta

+0

@Beta: pas nécessairement, on pourrait aussi penser à des objets Fonction étant définis pour ces accès. Il y a beaucoup de possibilités. –

+0

@ Matthieu M .: Je ne comprends pas. Je ne peux penser qu'à deux façons de le faire sans écrire une routine séparée pour chaque domaine de chaque classe: 1) modifier les classes ou 2) en utilisant des macros [shudder]. J'apprendrais volontiers une autre façon ... – Beta

Répondre

0

Donc, la réponse à la question principale, comment mapper un argument d'exécution à un attribut class, est You Can not

Qu'est-ce que vous cherchez est généralement appelé Runtime type d'information (RTTI pour faire court), c'est la capacité de récupérer des informations sur types à runtime.

C++ ne prend pas en charge RTTI. Pour le meilleur ou pour le pire, ce n'est tout simplement pas la fin de l'histoire.

Par conséquent, vous devrez inventer quelque chose de votre propre. En gros traits, vous allez mettre toutes vos classes B implémenter une interface qui aura une méthode comme getClass(string className), qui retournera le pointeur vers votre objet A, qui à son tour implémentera également une autre interface qui aura une méthode comme getAttribute(string attrName), puis vous serez en mesure de le comparer à votre valeur. Une autre possibilité est pour A d'avoir la méthode compareToValue(string attrName, string value) au lieu de getAttribute, de sorte qu'il peut gérer la mécanique de la valeur au lieu de simplement faire des chaînes.

De plus, vous pouvez avoir les méthodes correspondantes getAllClasses et getAllAttributes pour récupérer les listes - si vous en avez besoin. En outre, si vous n'êtes pas lié au C++, d'autres plateformes supportent RTTI. Java et .NET seraient deux exemples.

1

Vous devrez stocker vos données dans une carte hashmap ou normale plutôt que sous forme de données C++ brutes. Il est impossible de passer de la chaîne "hai" à A.hai sans faire de switch/case/default sur toutes les valeurs possibles en C++. Comme pour la réponse précédente, c'est une réflexion, pas une RTTI. C++ a RTTI - c'est ce à quoi sert typeid(). Mais, C++ ne le supporte certainement pas, peu importe ce que vous voulez l'appeler.

1

Vous avez certainement fourni suffisamment de détails :) J'espère que vous comprenez que ça ne va pas être facile. Ce que vous cherchez est appelé «Réflexion», la capacité d'un objet à se décrire.

La réflexion est difficile, mais cela peut être fait!

Je suis normalement un fan de metatemplates et autres joyeusetés, mais cette fois je vais vous proposer un OO-Way pur. C'est intrisique dans le sens où vous devrez réellement modifier vos objets pour le supporter.

L'idée de base est de le faire comme les langues qui supportent cet idiome le font nativement.

1- Nous adaptons les types de base

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- Ensuite, nous construisons une classe qui sera en mesure de tenir tous ces

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- Utilisation:

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

Il y a probablement beaucoup d'autres façons, plus ou moins automatisées. Notamment on pourrait penser à des structures d'adaptation sur des classes existantes. Quelque chose de similaire à la macro BOOST_FUSION_ADAPT_STRUCT mais avec la valeur ajoutée d'avoir le nom de la structure donnée.

Je crains que cela pourrait se révéler un peu plus Obscur cependant, pour aucun gain évident ... aussi, je dois ajouter que, si possible, vous pourriez être prêt à code directement dans une langue qui prend en charge nativement :)