2009-11-06 9 views
5

Mam strukturę z niektórymi członkami, które chcę móc uzyskać i ustawić z łańcucha. Biorąc pod uwagę, że C++ nie ma żadnej introspekcji, potrzebuję kreatywnego rozwiązania z makrami, operatorem naciągania i może boost::bind. Nie potrzebuję pełnej serializacji lub introspekcji, bardziej "introspekcyjno-lite". mieć coś na wzór tego:Czy istnieje dobry sposób ustawiania zmiennych składowych C/C++ z reprezentacji ciągów? (introspection-lite)

struct MyType { 
    int fieldA; 
    int fieldB; 
}; 
DECLARE_STRING_MAP(MyType,fieldA); 
DECLARE_STRING_MAP(MyType,fieldB); 

MyType t; 
SET_VALUE_FROM_STRING(MyType,t,"fieldA","3")  

Zamiast mieć ogromny if oświadczenie.

Każdy pomysł, czy jest to miłe rozwiązanie?

pytanie pokrewne: Object Reflection

EDIT: Dzięki maxim1000 dla 'mapę do typu int *' :: sztuczki - to pracował dla mnie:

#define DEFINE_LOOKUP_MAP(Type) std::map<AnsiString,int Type::*> mapper 
#define ADD_FIELD_MAPPING(Type, Field) mapper[#Field]=&Type::Field 
#define SET_FIELD_FROM_MAP(Type, Field, var, value) var.*(mapper[#Field])=value  

DEFINE_LOOKUP_MAP(MyType); 
ADD_FIELD_MAPPING(MyType, fieldA); 
ADD_FIELD_MAPPING(MyType, fieldB); 

SET_FIELD_FROM_MAP(MyType, fieldA, obj, 3); 
+0

Przypuszczam, że chcesz napisać SET_VALUE_FROM_STRING (MyType, t , currentAttr, "3"), jak również? –

+0

boost :: lexical_cast może być również użyteczny. –

+0

@Matthieu: tak, trzeci argument może być zmienną łańcuchową, np. Mogę przetworzyć tekst w postaci 'fieldA = 3, fieldB = 10' i wywołać tę funkcję @ltcmelo - tak,' lexical_cast' może Przydaj się do konwersji typu, ale załóżmy na chwilę, że wiem, że wartości zawsze będą int. –

Odpowiedz

5

Jeśli wszystkie z nich mają samego typu, można użyć coś takiego:

std::map<std::string,int MyType::*> mapper; 
mapper["fieldA"]=&MyType::fieldA; 
mapper["fieldB"]=&MyType::fieldB; 
... 
MyType obj; 
obj.*(mapper["fieldA"])=3; 
+0

To z pewnością jest to rodzaj składni, którą miałem na myśli, jeśli chodzi o powiązanie z 'MyType :: fieldA' w ten sposób, jednak zastanawiam się, czy da się to zrobić bez konieczności tworzenia mapy –

+1

@the_mandrill: Niektóre jawne mapowanie musi zająć miejsce, ponieważ nazwy zmiennych są "tracone" podczas kompilacji. – luke

+1

@ maxim1000: Jedną z wad tego rozwiązania jest niebezpieczeństwo określenia niepoprawnego ciągu znaków (tj. Nieobecnego na mapie), a następnie awarii w przypadku dereferencji zerowej. – luke

1

Jeśli nie są skłonni zmienić struct na coś innego, naprawdę nie masz wyboru - jesteś g Potrzeba dużego stwierdzenia, aby określić, z którym polem mamy do czynienia. Możesz ukryć (i ułatwić pisanie) za pomocą makr, ale jest to ta sama struktura i musisz po prostu sobie z tym poradzić.

Oto przykład tego, jak możesz napisać to makro - upraszcza ono korzystanie, ale nadal nie jest "krótkie" w żaden sposób.

//Assumption: at the time you want to use this, you've got two strings, one with the 
// name of the field to set (key), one with the value to set (value). I also assume 

typedef struct MyType { 
    int fieldA; 
    int fieldB; 
} MyType; 

// fldnamedef - name of the field in the structure definition (const char *) 
// convfunc - name of a function that takes a value, returns a fldtypedef 
// s - structure to put data into 
// key - const char * pointing to input field name 
// value - const char * pointing to input field value 
#define IF_FIELD_SET(fldnamedef, convfunc, s, key, value) {\ 
    if (strcmp(#fldnamedef, key) == 0) {\ 
    s.fldnamedef = convfunc(value);\ 
    }\ 
} 


int main() 
{ 
    MyType t={0,0}; 

    IF_FIELD_SET(fieldA, atoi, t, "fieldA", "2"); 

    printf("%d,%d\n",t.fieldA, t.fieldB); 
} 

A oto wyjście pre-processor, że linia IF_FIELD_SET zamienia:

{ if (strcmp("fieldA", "fieldA") == 0) { t.fieldA = atoi("2"); }}; 
+0

Myślę, że coś przeoczyłeś: "fldnamedef" czyni to bezużytecznym, to tylko o wiele dłuższy sposób pisania ' t.fieldA = atoi ("2"); '... Nie ma tam introspekcji. –

+0

C++ nie robi introspekcji w sposób, w jaki OP naprawdę potrzebuje. Opcje OP są zawijane w ifs w makrach lub zmieniają struktury danych. Ta odpowiedź jest jednym ze sposobów na zawarcie ifs w makrach, aby nieco łatwiej było z nimi pracować. –

0

To jest mniej więcej to, co operator "< <" jest. Niestety, język nie zapewnia domyślnej wersji dla struktur i klas, podobnie jak w przypadku operatora przypisania, ale można łatwo stworzyć własny.

0

Jeśli chcesz odejść od struktury na inny typ danych, masz kilka różnych opcji.

Jeśli pola są tego samego typu, po prostu użyj mapę STL:

typedef std :: map MyType;

MyType t; 

t["fieldA"] = atoi("3"); 
printf("%d\n", t["fieldA"]); 

Jeśli są różnych typów, a następnie można przejść z konwersji wartości jeśli wziąć ich struktury:

typedef std::map<std::string, std::string> MyType; 

MyType t; 
t["fieldA"] = "3"; 

printf("%d\n", atoi(t["fieldA"])); 

Można owinąć GET i konwersji w dziedzinie specyficznych makra, aby ułatwić pisanie.

typedef std::map<std::string, std::string> MyType; 
#define fieldA(v) atoi(v["fieldA"]) 

MyType t; 
t["fieldA"] = "3"; 

printf("%d\n", fieldA(v)); 

Ma to tę wadę, że nie przypomina dostępu do elementu konstrukcyjnego.

Możesz spróbować uczynić z MyType klasę i używać indywidualnych funkcji dla każdego pola. To przynajmniej pozwala uzyskać różne typy na pole, ale nadal będziesz musiał mieć duży blok ifs do zrobienia zestawu. Oczywiście, ponieważ możesz umieścić to w obiekcie, będzie łatwiejszy w użyciu. Oczywiście, włączyłeś dostęp do pola strukturalnego do wywołań metod obiektowych. Mimo to jest całkiem łatwy w użyciu i może ci coś kupić.

class MyType { 
public: 
    set(std::string key, std::string value) { 
    if (key == "fieldA") m_fieldA = atoi(value.c_str()); 
    if (key == "fieldB") m_fieldB = atoi(value.c_str()); 
    }; 

    int fieldA() { return m_fieldA; }; 
    int fieldB() { return m_fieldB; }; 
private: 
    int m_fieldA; 
    int m_fieldB; 
}; 

MyType t; 
t.set("fieldA", "3"); 
printf("%d\n", t.fieldA()); 
0

Czy istnieje jakiś powód, dla którego słownik/mapa nie działałaby? Możesz mieszać łańcuchy, aby szybciej wyszukiwać.

1

Emulacja introspekcji? To na pewno brzmi jak wyzwanie.

Interfejs naprawdę nie proszę mnie, więc chciałbym zaproponować alternatywę:

struct MyType 
{ 
    int fieldA; 
    int fieldB; 

    void setField(std::string const& field, std::string const& value); 
}; 

Teraz wyzwaniem jest dla setField do dobrania odpowiedniej dziedzinie, a nawet mapę wydaje się właściwe. Jednak musimy gdzieś opakować informacje o typie (chyba że planujesz używać tylko ints, w takim przypadku ... nie ma trudności), więc mapa funktorów jest w porządku.

static std::map<std::string, Functor<MyType>*> M_Map; 

// where Functor is 

template <class Type> 
struct Functor 
{ 
    virtual void set(Type& t, std::string const& value) const = 0; 
}; 

// And a specialization would be 
struct SetfieldA : public Functor<MyType> 
{ 
    virtual void set(MyType& t, std::string const& value) const 
    { 
    std::istringstream stream(value); 
    stream >> t.fieldA; 
    // some error handling could be welcome there :) 
    } 
}; 

Uwaga wykorzystanie std::istringstream, teraz można obsługiwać wszelkiego rodzaju, o ile prawidłowo współdziałać z std::istream. W ten sposób można obsługiwać zdefiniowane przez użytkownika klasy.

Oczywiście w tym przypadku chodzi o automatyzację!

I automatyzacja jak w makrach.

#define INTROSPECTED(MyType_)             \ 
    private:                  \ 
    typedef Functor<MyType_> intro_functor;          \ 
    typedef std::map<std::string, intro_functor const*> intro_map;    \ 
    static intro_map& IntroMap() { static intro_map M_; return M_; }    \ 
    public:                  \ 
    static void IntroRegister(std::string const& field, intro_functor const* f){ \ 
     IntroMap()[field] = f; }             \ 
    void setField(std::string const& field, std::string const& value) {   \ 
     intro_map::const_iterator it = IntroMap().find(field);      \ 
     if (it != IntroMap().end()) it->second->set(*this, value); } 

#define INTROSPECT_FIELD(Class_, Name_)           \ 
    struct Set##Name_: public Functor<Class_> {         \ 
    virtual void set(Class_& t, std::string const& value) {      \ 
     std::istringstream stream(value); stream >> t.Name_; } } Setter##Name_; \ 
    Class_::IntroRegister(#Name_, Setter##Name_) 

Wykorzystanie tak:

// myType.h 
struct MyType 
{ 
    INTROSPECTED(MyType); 

    int fieldA; 
    int fieldB; 
}; 

// myType.cpp 
INTROSPECT_FIELD(MyType, fieldA); 
INTROSPECT_FIELD(MyType, fieldB); 

// Any file 
MyType t; 
t.set("fieldA", "3"); 

Oczywiście zwykle stosuje się zastrzeżenie: u mojej głowie, nigdy nie opracowano go może zabić kocięta i gorzej.

2

Potrafię wymyślić dwa rozwiązania.

używać makr stworzyć definicję struktury i jej mapę z tego samego źródła

za pomocą makr i przerabianiu swoją definicję struktury, można użyć techniki opisane w niniejszym doskonałą odpowiedź bez oddzielnie deklarując mapę.

Przepisz swoją definicję struktury takiego, i umieścić go w nagłówku sama:

BEGIN_STRUCT(MyType) 
FIELD(int, fieldA); 
FIELD(int, fieldB); 
END_STRUCT 

Następnie #include je dwukrotnie. Przed #including go po raz pierwszy:

#define BEGIN_STRUCT(x) struct x { 
#define FIELD(x, y) x y; 
#define END_STRUCT }; 

przed #including go po raz drugi:

#define BEGIN_STRUCT(x) namespace x ## Mapping { typedef x MappedType; 
#define FIELD mapper[#x]=&MappedType::x; 
#define END_STRUCT } 

Nie testowałem tego, więc kilka szczegółów może być wyłączony.

Jeśli makra są zbanowane w twoim środowisku, możesz zamiast tego utworzyć definicję struktury i jej mapę z dowolnego narzędzia zewnętrznego (Perl, Cog Pythona itp.).

użyć biblioteki C++ dla odbicia

Chociaż C++ nie bezpośrednio realizować odbicie lub introspekcji, dodatek biblioteki są dostępne. Użyłem biblioteki ROOT's Reflex z dobrymi wynikami.

4

Śmierć makrom.

Niektóre makro-wolny kod Użyłem w przeszłości za wiązanie nazwy struc członków i konwersji typów non-string do łańcucha:

#include <map> 
#include <string> 
#include <sstream> 

template<class STRUC> 
struct Field 
{ 
    virtual void set (STRUC& struc, const std::string& value) const = 0; 
}; 

template<class STRUC, class FIELDTYPE> 
struct FieldImpl : public Field<STRUC> 
{ 
    typedef FIELDTYPE (STRUC::*MemberPtr); 

    FieldImpl (MemberPtr memberPtr) {memberPtr_ = memberPtr;} 

    virtual void set (STRUC& struc, const std::string& value) const 
    { 
     std::istringstream iss (value); 
     iss >> struc.*memberPtr_; 
    } 

private: 
    MemberPtr memberPtr_; 
}; 

template<class STRUC> 
class FieldMap 
{ 
private: 
    typedef std::map<std::string, Field<STRUC>*> FieldNameMap; 
    FieldNameMap fieldMap_; 

public: 
    ~FieldMap() 
    { 
     // delete fieldMap_ members. 
    } 

    void bind (const std::string& name, Field<STRUC>* field) 
    { 
     fieldMap_[name] = field; 
    } 

    template<typename FIELDTYPE> 
    void bind (const std::string& name, FIELDTYPE (STRUC::* member)) 
    { 
     fieldMap_[name] = new FieldImpl<STRUC, FIELDTYPE> (member); 
    } 

    void setValue (STRUC& struc, const std::string& name, const std::string& value) 
    { 
     FieldNameMap::const_iterator iter = fieldMap_.find (name); 

     if (iter == fieldMap_.end()) 
      throw std::runtime_error (std::string ("No field binding found for ") + name); 

     (*iter).second->set (struc, value); 
    } 
}; 

struct Test 
{ 
    int id; 
    double value; 
    std::string tag; 
}; 

int main (int argc, char* argv[]) 
{ 
    FieldMap<Test> fieldMap; 
    fieldMap.bind ("id", &Test::id); 
    fieldMap.bind ("value", &Test::value); 
    fieldMap.bind ("tag", &Test::tag); 

    Test test; 

    fieldMap.setValue (test, "id", "11"); 
    fieldMap.setValue (test, "value", "1234.5678"); 
    fieldMap.setValue (test, "tag", "hello"); 

    return 0; 
} 
Powiązane problemy