Инициализация структуры с использованием массива - PullRequest
4 голосов
/ 13 ноября 2008

У меня есть пара массивов:

const string a_strs[] = {"cr=1", "ag=2", "gnd=U", "prl=12", "av=123", "sz=345", "rc=6", "pc=12345"};
const string b_strs[] = {"cr=2", "sz=345", "ag=10", "gnd=M", "prl=11", "rc=6", "cp=34", "cv=54", "av=654", "ct=77", "pc=12345"};

, который мне затем нужно разобрать для '=', а затем поместить значения в структуру. (ключ rc соответствует ключу fc в структуре), который имеет вид:

struct predict_cache_key {
    pck() :
        av_id(0),
        sz_id(0),
        cr_id(0),
        cp_id(0),
        cv_id(0),
        ct_id(0),
        fc(0),
        gnd(0),
        ag(0),
        pc(0),
        prl_id(0)
    { }

    int av_id;
    int sz_id;
    int cr_id;
    int cp_id; 
    int cv_id;
    int ct_id;
    int fc;
    char gnd;
    int ag;
    int pc;
    long prl_id;
};

Проблема, с которой я сталкиваюсь, состоит в том, что массивы не находятся в последовательности или в той же последовательности, что и поля struct. Итак, мне нужно проверить каждый из них, а затем придумать схему, чтобы поместить это в структуру.

Любая помощь в использовании C или C ++ для решения вышеуказанного?

Ответы [ 7 ]

6 голосов
/ 13 ноября 2008

Возможно, я не правильно понял, но очевидное решение состоит в том, чтобы разбить каждый элемент массива на key и value, а затем написать последовательность lo-o-ong if-else-if-else ..., такую ​​как

if (!strcmp(key, "cr"))
   my_struct.cr = value;
else if (!strcmp(key, "ag"))
   my_struct.ag = value;
...

Вы можете автоматизировать создание такой последовательности с помощью препроцессора C, например,

#define PROC_KEY_VALUE_PAIR(A) else if (!strcmp(key,#A)) my_struct.##A = value

Из-за ведущего else вы пишете код так:

if (0);
PROC_KEY_VALUE_PAIR(cr);
PROC_KEY_VALUE_PAIR(ag);
...

Единственная проблема в том, что некоторые из ваших структурных полей имеют суффикс _id - для них вам нужно создать немного другой макрос, который вставит _id суффикс

3 голосов
/ 13 ноября 2008

Я написал небольшой код, который позволяет вам инициализировать поля, не беспокоясь о том, выходят ли ваши поля из строя при инициализации.

Вот как вы используете его в своем коде:

/* clients using the above classes derive from lookable_fields */
struct predict_cache_key : private lookable_fields<predict_cache_key> {
    predict_cache_key(std::vector<std::string> const& vec) {
        for(std::vector<std::string>::const_iterator it = vec.begin();
            it != vec.end(); ++it) {
            std::size_t i = it->find('=');
            set_member(it->substr(0, i), it->substr(i + 1));
         }
    }

    long get_prl() const {
        return prl_id;
    }

private:

    /* ... and define the members that can be looked up. i've only
     * implemented int, char and long for this answer. */
    BEGIN_FIELDS(predict_cache_key)
        FIELD(av_id);
        FIELD(sz_id);
        FIELD(gnd);
        FIELD(prl_id);
    END_FIELDS()

    int av_id;
    int sz_id;
    char gnd;
    long prl_id;
    /* ... */
};

int main() {
    std::string const a[] = { "av_id=10", "sz_id=10", "gnd=c",
                              "prl_id=1192" };
    predict_cache_key haha(std::vector<std::string>(a, a + 4));
}

Рамки ниже

template<typename T>
struct entry {
    enum type { tchar, tint, tlong } type_name;

    /* default ctor, so we can std::map it */
    entry() { }

    template<typename R>
    entry(R (T::*ptr)) {
        set_ptr(ptr);
    }

    void set_ptr(char (T::*ptr)) {
        type_name = tchar;
        charp = ptr;
    };

    void set_ptr(int (T::*ptr)) {
        type_name = tint;
        intp = ptr;        
    };

    void set_ptr(long (T::*ptr)) {
        type_name = tlong;
        longp = ptr;        
    };

    union {
        char (T::*charp);
        int  (T::*intp);
        long (T::*longp);
    };
};

#define BEGIN_FIELDS(CLASS)       \
    friend struct lookable_fields<CLASS>; \
    private:                      \
    static void init_fields_() {   \
        typedef CLASS parent_class;

#define FIELD(X) \
    lookable_fields<parent_class>::entry_map[#X].set_ptr(&parent_class::X)

#define END_FIELDS() \
    }                                                                              

template<typename Derived>
struct lookable_fields {
protected:
    lookable_fields() {
        (void) &initializer; /* instantiate the object */
    }

    void set_member(std::string const& member, std::string const& value) {
        typename entry_map_t::iterator it = entry_map.find(member);
        if(it == entry_map.end()) {
            std::ostringstream os;
            os << "member '" << member << "' not found";
            throw std::invalid_argument(os.str());
        }

        Derived * derived = static_cast<Derived*>(this);

        std::istringstream ss(value);
        switch(it->second.type_name) {
        case entry_t::tchar: {
            /* convert to char */
            ss >> (derived->*it->second.charp);
            break;
        }
        case entry_t::tint: {
            /* convert to int */
            ss >> (derived->*it->second.intp);
            break;
        }
        case entry_t::tlong: {
            /* convert to long */
            ss >> (derived->*it->second.longp);
            break;
        }
        }
    }

    typedef entry<Derived> entry_t;
    typedef std::map<std::string, entry_t> entry_map_t;
    static entry_map_t entry_map;

private:
    struct init_helper {
        init_helper() {
            Derived::init_fields_();
        }
    };

    /* will call the derived class's static init function */
    static init_helper initializer;
};

template<typename T> 
std::map< std::string, entry<T> > lookable_fields<T>::entry_map;

template<typename T> 
typename lookable_fields<T>::init_helper lookable_fields<T>::initializer;

Он работает с использованием менее известных указателей на члены-данные, которые вы можете взять из класса, используя синтаксис &classname::member.

3 голосов
/ 13 ноября 2008

Это не должно быть слишком сложно. Ваша первая проблема заключается в том, что у вас нет массива фиксированного размера, поэтому вам нужно будет передать размер массива или, что я бы предпочел, сделать массивы с нулевым символом в конце, например

const string a_strs[] = {"cr=1", "ag=2", "gnd=U", NULL};

Тогда я бы написал (приватную) вспомогательную функцию, которая будет анализировать строку:


bool
parse_string(const string &str, char *buffer, size_t b_size, int *num)
{
    char *ptr;

    strncpy(buffer, str.c_str(), b_size);
    buffer[b_size - 1] = 0;

    /* find the '=' */
    ptr = strchr(buffer, '=');

    if (!ptr) return false;

    *ptr = '\0';
    ptr++;

    *num = atoi(ptr);

    return true;
}

тогда вы можете делать то, что предложил qrdl.

в простом цикле for:


for (const string *cur_str = array; *cur_str; cur_str++)
{
   char key[128];
   int value = 0;

   if (!parse_string(*cur_string, key, sizeof(key), &value)
       continue;

   /* and here what qrdl suggested */
   if (!strcmp(key, "cr")) cr_id = value;
   else if ...
}

РЕДАКТИРОВАТЬ: вам, вероятно, следует использовать long вместо int и atol вместо atoi, потому что ваш prl_id имеет тип long. Во-вторых, если после '=' могут быть неправильные форматированные числа, вы должны использовать strtol, который может отлавливать ошибки.

1 голос
/ 13 ноября 2008

Действительно, как многие ответили, существует необходимость отделить проблему синтаксического анализа от проблемы построения объекта. Для этого хорошо подходит шаблон Factory.

Библиотека Boost.Spirit также очень элегантно решает проблему синтаксического анализа-> функции (использует запись EBNF).

Мне всегда нравится отделять «бизнес-логику» от кода платформы.

Вы можете достичь этого, начав писать «что вы хотите» очень удобным способом и оттуда перейти к «как вы это делаете».

  const CMemberSetter<predict_cache_key>* setters[] = 
  #define SETTER( tag, type, member ) new TSetter<predict_cache_key,type>( #tag, &predict_cache_key::##member )
  { SETTER( "av", int, av_id )
  , SETTER( "sz", int, sz_id )
  , SETTER( "cr", int, cr_id )
  , SETTER( "cp", int, cp_id )
  , SETTER( "cv", int, cv_id )
  , SETTER( "ct", int, ct_id )
  , SETTER( "fc", int, fc )
  , SETTER( "gnd", char, gnd )
  , SETTER( "ag", int, ag )
  , SETTER( "pc", int, pc )
  , SETTER( "prl", long, prl_id )
  };

  PCKFactory<predict_cache_key> factory ( setters );

  predict_cache_key a = factory.factor( a_strs );
  predict_cache_key b = factory.factor( b_strs );

И рамки для достижения этого:

  // conversion from key=value pair to "set the value of a member"
  // this class merely recognises a key and extracts the value part of the key=value string
  //
  template< typename BaseClass >
  struct CMemberSetter {

    const std::string key;
    CMemberSetter( const string& aKey ): key( aKey ){}

    bool try_set_value( BaseClass& p, const string& key_value ) const {
      if( key_value.find( key ) == 0 ) {
        size_t value_pos = key_value.find( "=" ) + 1;
        action( p, key_value.substr( value_pos ) );
        return true;
      }
      else return false;
    }
    virtual void action( BaseClass& p, const string& value ) const = 0;
  };

  // implementation of the action method
  //
  template< typename BaseClass, typename T >
  struct TSetter : public CMemberSetter<BaseClass> {
    typedef T BaseClass::*TMember;
    TMember member;

    TSetter( const string& aKey, const TMember t ): CMemberSetter( aKey ), member(t){}
    virtual void action( BaseClass& p, const std::string& valuestring ) const {
      // get value
      T value ();
      stringstream ( valuestring ) >> value;
      (p.*member) = value;
    }
  };


  template< typename BaseClass >
  struct PCKFactory {
    std::vector<const CMemberSetter<BaseClass>*> aSetters;

    template< size_t N >
    PCKFactory( const CMemberSetter<BaseClass>* (&setters)[N] )
      : aSetters( setters, setters+N ) {}

    template< size_t N >
    BaseClass factor( const string (&key_value_pairs) [N] ) const {
      BaseClass pck;

      // process each key=value pair
      for( const string* pair = key_value_pairs; pair != key_value_pairs + _countof( key_value_pairs); ++pair ) 
      {
        std::vector<const CMemberSetter<BaseClass>*>::const_iterator itSetter = aSetters.begin();
        while( itSetter != aSetters.end() ) { // optimalization possible
          if( (*itSetter)->try_set_value( pck, *pair ) )
            break;
          ++itSetter;
        }
      }

      return pck;
    }
  };
0 голосов
/ 28 ноября 2009

попробовал вашу идею и получил

error: ISO C++ forbids declaration of ‘map’ with no type

в Linux Ubuntu Eclipse CDT.

Я хочу уведомить, что следует включить <map> в файл "* .h" чтобы использовать ваш код без этого сообщения об ошибке.

#include <map>

// a framework

template<typename T>
struct entry {
    enum type { tchar, tint, tlong } type_name;

    /* default ctor, so we can std::map it */
    entry() { }

    template<typename R>
    entry(R (T::*ptr)) {

и т. Д. И т. Д. ......

0 голосов
/ 13 ноября 2008

Если бы я делал это по прямой С, я бы не использовал мать всех, если бы. Вместо этого я бы сделал что-то вроде этого:

typedef struct {
    const char *fieldName;
    int structOffset;
    int fieldSize;
} t_fieldDef;

typedef struct {
    int fieldCount;
    t_fieldDef *defs;
} t_structLayout;

t_memberDef *GetFieldDefByName(const char *name, t_structLayout *layout)
{
    t_fieldDef *defs = layout->defs;
    int count = layout->fieldCount;
    for (int i=0; i < count; i++) {
        if (strcmp(name, defs->fieldName) == 0)
            return defs;
        defs++;
    }
    return NULL;
}

/* meta-circular usage */
static t_fieldDef metaFieldDefs[] = {
    { "fieldName", offsetof(t_fieldDef, fieldName), sizeof(const char *) },
    { "structOffset", offsetof(t_fieldDef, structOffset), sizeof(int) },
    { "fieldSize", offsetof(t_fieldDef, fieldSize), sizeof(int) }
};
static t_structLayout metaFieldDefLayout =
    { sizeof(metaFieldDefs) / sizeof(t_fieldDef), metaFieldDefs };

Это позволяет вам искать поле по имени во время выполнения с компактной коллекцией структуры структуры. Это довольно легко поддерживать, но мне не нравится sizeof(mumble) в фактическом коде использования - это требует, чтобы все определения структуры были помечены комментариями, гласящими: «Не изменяйте типы или содержимое без изменения их t_fieldDef массив для этой структуры ". Там также должна быть проверка NULL.

Я бы также предпочел, чтобы поиском был либо бинарный поиск, либо хеш, но, вероятно, этого достаточно для большинства случаев. Если бы я делал хеш, я бы поместил указатель на хеш-таблицу NULL в t_structLayout и при первом поиске построил хеш.

0 голосов
/ 13 ноября 2008

Проблема в том, что у вас нет метаинформации для ссылки на элементы структуры во время выполнения (что-то вроде structVar. $ ElementName = ..., где $ ElementName - это не имя элемента, а переменная (char?), Содержащая элемент имя, которое следует использовать). Мое решение было бы добавить эту метаинформацию. Это должен быть массив со смещением элементов в структуре.

Решение Quick-n-Dirty: вы добавляете массив со строками, полученный код должен выглядеть следующим образом:

const char * wordlist[] = {"pc","gnd","ag","prl_id","fc"};
const int  offsets[] = { offsetof(mystruct, pc), offsetof(mystruct, gnd), offsetof(mystruct, ag), offsetof(mystruct, prl_id), offsetof(mystruct, fc)};
const int sizes[] = { sizeof(mystruct.pc), sizeof(mystruct.gnd), sizeof(mystruct.ag), sizeof(mystruct.prl_id), sizeof(mystruct.fc)}

чтобы ввести что-то, вы бы хотели что-то вроде этого:

index = 0;
while (strcmp(wordlist[index], key) && index < 5)
    index++;
if (index <5)
   memcpy(&mystructvar + offsets[index], &value, sizes[index]);
else
   fprintf(stderr, "Key not valid\n"); 

Этот цикл для вставок может быть дорогостоящим, если у вас большие структуры, но C допускает индексацию массива со строками. Но информатика нашла решение этой проблемы: идеальные хеши.

Так что потом это будет выглядеть так:

hash=calc_perf_hash(key);
memcpy(&mystruct + offsets[hash], &value, sizes[hash]);

Но как получить эти совершенные хеш-функции (я назвал это calc_perf_hash)? Для этого существуют алгоритмы, в которых вы просто вводите свои ключевые слова, и функции выходят, и, к счастью, кто-то даже запрограммировал их: ищите инструмент / пакет "gperf" в вашей любимой ОС / дистрибутиве. Там вы просто вводите 6 имен элементов, и он выводит вам готовый к использованию C-код для идеальной хэш-функции (по умолчанию генерирует функцию "hash", которая возвращает хэш, и функцию "in_word_set", которая решает, является ли данная ключ находится в списке слов). Поскольку хэш находится в другом порядке, вы, конечно, должны инициализировать массивы offsetof и size в порядке хэшей.

Другая проблема, с которой вы столкнулись (и которую другие ответы не учитывают), - это преобразование типов. Остальные делают задание, у меня есть (не лучше) memcopy. Здесь я бы предложил вам изменить массив размеров на другой:

const char * modifier[]={"%i","%c", ...

Где каждая строка описывает модификатор sscanf для чтения. Таким образом, вы можете заменить назначение / копию на

sscanf(valueString, modifier[hash], &mystructVar + offsets(hash));

Cf, конечно, вы можете изменить здесь, включив "element =" в строку или подобное. Таким образом, вы можете поместить всю строку в значение и не нужно предварительно обрабатывать ее, я думаю, что это сильно зависит от вашей разборки.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...