5

文字列から取得および設定できるようにしたいいくつかのメンバーを持つ構造体があります。C++ にはイントロスペクションがないことを考えると、マクロ、文字列化演算子を使用した創造的なソリューションが必要だと思います。おそらくboost::bind.、完全なシリアル化やイントロスペクションは必要なく、「イントロスペクション ライト」が必要です。

私はこれに沿って何かをしたいと思います:

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

if大げさな発言をするのではなく。

これに対するきちんとした解決策があるかどうか、何か考えはありますか?

関連する質問:オブジェクトの反射

編集:「map to int Type::*」トリックについて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);
4

8 に答える 8

6

それらのすべてが同じタイプの場合、次のようなものを使用できます。

std::map<std::string,int MyType::*> mapper;
mapper["fieldA"]=&MyType::fieldA;
mapper["fieldB"]=&MyType::fieldB;
...
MyType obj;
obj.*(mapper["fieldA"])=3;
于 2009-11-06T14:35:36.163 に答える
4

マクロに死を。

名前を struc メンバーにバインドし、非文字列型を文字列に変換するために過去に使用したマクロのないコード:

#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;
}
于 2009-11-06T17:14:49.040 に答える
2

私は2つの解決策を考えることができます。

マクロを使用して、同じソースから構造定義とそのマップを作成します

マクロを使用して構造定義を作り直すことで、マップを個別に宣言することなく、この優れた回答で説明されている手法を使用できます。

構造定義を次のように書き直して、それ自体をヘッダーに入れます。

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

次に #include を 2 回実行します。初めて #include する前に:

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

2 回目に #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 の Reflexライブラリを使用して良好な結果を得ました。

于 2009-11-06T15:23:29.967 に答える
1

イントロスペクション エミュレーション ? それは挑戦のように聞こえますが、それは確かです。

インターフェイスはあまり気に入らないので、別の方法を提案します。

struct MyType
{
  int fieldA;
  int fieldB;

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

ここでの課題は、setField適切なフィールドを選択することであり、実際にマップが適切であるように思われます。ただし、型情報をどこかにカプセル化する必要があるため (int のみを使用する予定がない限り、その場合は問題はありません)、ファンクタのマップが適切です。

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::istringstreamstd::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");

もちろん、通常の警告が適用されます。頭のてっぺんから、コンパイルしたことがなく、子猫を殺す可能性があり、さらに悪いことです。

于 2009-11-06T15:13:45.710 に答える
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"); }};
于 2009-11-06T14:41:43.873 に答える
0

辞書/マップが機能しない理由はありますか? 文字列をハッシュして検索を高速化できます。

于 2009-11-06T15:12:30.597 に答える
0

これは多かれ少なかれ「<<」演算子の目的です。残念なことに、代入演算子のように構造体とクラスのデフォルト バージョンが言語で提供されていませんが、独自のバージョンを簡単に作成できます。

于 2009-11-06T14:44:47.563 に答える
0

構造体から別のデータ型に変更する場合は、いくつかの異なるオプションがあります。

フィールドがすべて同じ型の場合は、STL マップを使用してください。

typedef std::map 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 と conversion をフィールド固有のマクロでラップして、記述を少し簡単にすることができます。

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());
于 2009-11-06T15:00:56.533 に答える