2009-11-06 1 views
5

문자열에서 가져 와서 설정할 수있는 멤버가있는 구조체가 있습니다. C++가 어떤 인트로 스펙 션을 가지고 있지 않다면 나는 매크로, 문자열 연산자 및 어쩌면 boost::bind.과 함께 창의적인 솔루션이 필요하다고 생각한다. 전체 직렬화 또는 인트로 스펙 션, 더 많은 '인트로 스펙 션 라이트'가 필요 없다.문자열 표현에서 C/C++ 멤버 변수를 설정하는 좋은 방법이 있습니까? (introspection-lite)

내가 원한다. 이의 라인을 따라 뭔가를하기 : 거대한 if 문이보다는

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")  

.

깔끔한 해결책이 있습니까?

관련 질문 : Object Reflection

편집 : '* :: 형식을 int로 매핑'트릭에 대한 maxim1000에 감사합니다 -이 나를 위해 일한 :

#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

, currentAttr, "3")? –

+0

boost :: lexical_cast도 유용 할 수 있습니다. –

+0

@Matthieu : 네 번째 인자는 문자열 변수 일 수 있습니다. 예를 들어'fieldA = 3, fieldB = 10' 형식의 텍스트를 파싱하고이 함수를 호출 할 수 있습니다. @ltcmelo - 예,'lexical_cast'는 다음과 같이 할 수 있습니다. 형식 변환에 유용하지만, 그 순간 값이 항상 int가 될 것이라는 것을 알고 있다고 가정 해 봅시다. –

답변

5

그들 모두가있는 경우 g 당신이있어 - 당신이 뭔가에 구조체를 변경하고자하지 않는 경우

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

This 이것은 MyType :: fieldA와의 바인딩이라는 점에서 염두에 두었던 문법입니다. 그러나 맵의 생성을 요구하지 않고 할 수 있는지 궁금합니다. –

+1

@the_mandrill : 일부 명시적인 매핑이 필요합니다. 컴파일 타임에 변수 이름이 "손실"되기 때문에 작업 공간이 부족합니다. – luke

+1

@ maxim1000 :이 솔루션의 단점 중 하나는 올바르지 않은 멤버 문자열 (즉, 맵에 없음)을 지정하고이어서 null 참조 해제시 위험 할 수 있다는 것입니다. – luke

1

, 당신은 정말 선택의 여지가 없어 : 같은 종류는, 다음과 같이 사용할 수 있습니다 당신이 상대하고있는 분야를 알기 위해 큰 if 문이 필요합니다. 매크로로 매크로를 숨길 수 있으며 매크로를 작성하기가 쉽습니다. 구조는 동일하므로 처리해야합니다.

다음은 매크로를 작성하는 방법의 예입니다. 사용법은 간단하지만 어떤 방법으로도 '짧게'는 아닙니다.

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

그리고 여기에는 IF_FIELD_SET 라인으로 변하기 이전 프로세서 출력입니다 :

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

"fldnamedef"는 쓸모가 없어 보였습니다. t.fieldA = atoi ("2"); ... 거기에 내성이 없습니다. –

+0

C++은 OP가 실제로 필요한 방식으로 내부 검사를 수행하지 않습니다. OP의 선택 사항은 if를 매크로에 넣거나 데이터 구조를 전환하는 것입니다. 이 대답은 매크로에서 if를 감싸서 작업하기가 더 쉬워 지도록하는 한 가지 방법입니다. –

0

이것은 더 많거나 적은 "< <"연산자가 무엇인지. 슬프게도이 언어는 대입 연산자와 마찬가지로 구조체 및 클래스의 기본 버전을 제공하지 않지만 쉽게 충분히 만들 수 있습니다.

0

구조체에서 다른 데이터 형식으로 변경하려는 경우 몇 가지 옵션이 있습니다.

필드는 동일한 유형의 모든 경우, 그냥 STL 맵 사용

형식 정의 표준 : 맵합니다 MyType을;

MyType t; 

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

서로 다른 유형의 경우이 구조에서 그들을 걸릴 때, 당신은 값을 변환과 함께 갈 수 :

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

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

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

당신은 GET과의 변환 래핑 할 수 필드 별 매크로를 작성하여 작성하기가 쉽습니다.

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

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

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

구조 요소 액세스처럼 보이지 않는 단점이 있습니다.

MyType을 클래스로 만들고 각 필드에 개별 함수를 사용해보십시오. 적어도 필드 당 다른 유형을 얻을 수 있지만 세트를 수행하려면 여전히 if 블록을 사용해야합니다. 물론 객체에 넣을 수 있기 때문에 사용하기가 더 쉽습니다. 물론 구조체 필드 액세스를 객체 메소드 호출로 변환했습니다. 그래도 사용하기가 쉽고, 뭔가를 사 줄 수 있습니다.

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

사전 /지도가 작동하지 않는 이유가 있습니까? 문자열을 해시하여 조회를 빠르게 할 수 있습니다.

1

내성 에뮬레이션? 그것은 도전처럼 들리지만, 그것은 확실합니다.

인터페이스는 정말 나를 기쁘게하지 않는, 그래서 대안을 제안합니다 : setField 오른쪽 필드를 선택하는

struct MyType 
{ 
    int fieldA; 
    int fieldB; 

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

지금 도전이며, 실제로지도가 적절한 것 같다. 그러나 우리는 타입 정보를 어딘가에 캡슐화 할 필요가 있습니다 (int 만 사용하는 경우는 예외입니다 ... 아무런 문제가 없습니다). 그래서 펑터 (functor)의 맵이 순서에 있습니다.

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

std::istringstream의 사용은, 지금은 그들이 올바르게 std::istream와 상호 작용하는 모든 유형을 지원할 수 있습니다. 따라서 사용자 정의 클래스를 지원할 수 있습니다.

물론 여기서는 자동화에 관한 부분입니다.

매크로와 같은 자동화. 이 같은

#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_) 

사용법 : 그것을 컴파일하지, 내 머리 위로 떨어져 새끼 고양이 악화를 죽일 수 물론

// 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"); 

평소주의가 적용됩니다.

2

두 가지 해결책을 생각해 볼 수 있습니다. 별도로지도를 선언하지 않고이 우수한 대답에 설명 된대로

사용 매크로 구조 정의 및 매크로를 사용하여 구조 정의를 재 작업하여 동일한 소스

로부터지도를 만들려면 기술을 사용할 수 있습니다.

이처럼 구조 정의를 다시 작성하고, 자체 헤더에 넣어

:

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

을 다음 #INCLUDE가 두 번. 그것을 처음으로 #include하기 전에 :

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

그것을 두 번째 시간으로 #include하기 전에 :

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

내가 이것을 테스트하지, 그래서 몇 가지 세부 사항이 해제 될 수 있습니다.

환경에서 매크로가 금지 된 경우 원하는 외부 도구 (Perl, Python의 Cog 등)에서 구조 정의와 그 맵을 작성할 수 있습니다. C++ 직접 반사 또는 성찰을 구현하지 않지만

애드온 라이브러리를 사용할 수, C++

에 대한 반사 라이브러리를 사용합니다. 나는 ROOT's Reflex 라이브러리를 사용하여 좋은 결과를 얻었습니다.

4

매크로에 대한 죽음.

내가 문자열로 문자열이 아닌 유형의 구성원을 struc에 이름을 바인딩 및 변환 과거에 사용했던 일부 매크로없는 코드 : 나는 당신이, t (합니다 MyType을 SET_VALUE_FROM_STRING을 작성하려한다고 가정

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