Используйте указатель члена структуры, чтобы заполнить структуру в C ++ - PullRequest
0 голосов
/ 23 октября 2018

Итак, у меня есть следующее:

struct data_t {
    char field1[10];
    char field2[20];
    char field3[30];
};
const char *getData(const char *key);
const char *field_keys[] = { "key1", "key2", "key3" };

Этот код предоставлен моему, и я не могу его изменить.Это происходит из какого-то старого проекта C.

Мне нужно заполнить структуру, используя функцию getData с различными ключами, что-то вроде следующего:

struct data_t my_data;
strncpy(my_data.field1, getData(field_keys[0]), sizeof(my_data.field1));
strncpy(my_data.field1, getData(field_keys[1]), sizeof(my_data.field2));
strncpy(my_data.field1, getData(field_keys[2]), sizeof(my_data.field3));

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

struct data_t {
    char field1[10];
    char field2[20];
    char field3[30];
};

typedef char *(data_t:: *my_struct_member);
const std::vector<std::pair<const char *, my_struct_member>> mapping = {
    { "FIRST_KEY" , &my_struct_t::field1},
    { "SECOND_KEY", &my_struct_t::field2},
    { "THIRD_KEY",  &my_struct_t::field3},
};

int main()
{
    data_t data;

    for (auto const& it : mapping) {
        strcpy(data.*(it.second), getData(it.first));
        // Ideally, I would like to do
        // strlcpy(data.*(it.second), getData(it.first), <the right sizeof here>);
    }
}

Это, однако, имеет две проблемы:

  1. Это не компилируется :) Но я считаю, что должно быть легко решить.
  2. Я не уверен, как получить аргумент sizeof() для использования strncpy / strlcpy вместо strcpy.Я использую char * в качестве типа членов, поэтому я теряю информацию о типе длины каждого массива.С другой стороны, я не уверен, как использовать конкретные типы char[T] каждого члена, потому что, если каждый указатель члена структуры имеет свой тип, я не думаю, что смогу иметь их в std::vector<T>.

Ответы [ 5 ]

0 голосов
/ 23 октября 2018

Как объяснено в моем комментарии, если вы можете хранить достаточно информации для обработки поля в отображении, то вы можете написать функцию, которая делает то же самое.

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

template <std::size_t N>
void process_field(char (&dest)[N], const char * src)
{
    strlcpy(dest, getData(src), N);

    // more work with the field...
};

И затем просто, вместо вашего for цикла:

process_field(data.field1, "foo");
process_field(data.field2, "bar");
// ...

Обратите внимание, что количество строк являетсятак же, как с отображением (по одному на поле), так что это не хуже, чем решение с отображением с точки зрения повторения.

Теперь преимущества:

  • Прощепонять.

  • Быстрее: не требуется памяти для сохранения отображения, более легко оптимизируемой и т. д.

  • Позволяет писать различные функции для разныхполя, легко, если это необходимо.


Далее, если обе ваши строки известны во время компиляции, вы можете даже сделать:

template <std::size_t N, std::size_t M>
void process_field(char (&dest)[N], const char (&src)[M])
{
    static_assert(N >= M);
    std::memcpy(dest, src, M);

    // more work with the field...
};

Который будет всегда в безопасности, например:

process_field(data.field1, "123456789");  // just fits!
process_field(data.field1, "1234567890"); // error

Который имеет еще больше плюсов:

  • Путь быстрее, чем любой вариант strcpy (если вызов выполняется во время выполнения).

  • Гарантируется безопасность при компиляциивремя вместо времени выполнения.

0 голосов
/ 23 октября 2018

Вариантное решение на основе шаблонов:

struct my_struct_t {
    char one_field[30];
    char another_field[40];
};

template<typename T1, typename T2>
void do_mapping(T1& a, T2& b) {
    std::cout << sizeof(b) << std::endl;
    strncpy(b, a, sizeof(b));
}

template<typename T1, typename T2, typename... Args>
void do_mapping(T1& a, T2& b, Args&... args) {
    do_mapping(a, b);
    do_mapping(args...);
}

int main()
{
    my_struct_t ms;
    do_mapping(
        "FIRST_MAPPING",  ms.one_field, 
        "SECOND_MAPPING", ms.another_field
    );
    return 0;
}
0 голосов
/ 23 октября 2018

Поскольку data_t является структурой POD, вы можете использовать для этого offsetof().

const std::vector<std::pair<const char *, std::size_t>> mapping = {
    { "FIRST_FIELD" , offsetof(data_t, field1},
    { "SECOND_FIELD", offsetof(data_t, field2)}
};

Тогда цикл будет выглядеть так:

for (auto const& it : mapping) {
    strcpy(static_cast<char*>(&data) + it.second, getData(it.first));
}

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

0 голосов
/ 23 октября 2018

Для перебора элемента структуры вам нужно:

  1. смещение / указатель на начало этого элемента
  2. размер этого элемента

struct Map {
    const char *key;
    std::size_t offset;
    std::size_t size;
};

std::vector<Map> map = {
    { field_keys[0], offsetof(data_t, field1), sizeof(data_t::field1), },
    { field_keys[1], offsetof(data_t, field2), sizeof(data_t::field2), },
    { field_keys[2], offsetof(data_t, field3), sizeof(data_t::field3), },
};

как только мы получим, нам нужно strlcpy:

std::size_t mystrlcpy(char *to, const char *from, std::size_t max)
{

    char * const to0 = to;
    if (max == 0) 
        return 0;
    while (--max != 0 && *from) {
        *to++ = *from++;
    }
    *to = '\0';
    return to0 - to - 1;
}

Получив это, мы можем просто:

data_t data;

for (auto const& it : map) {
    mystrlcpy(reinterpret_cast<char*>(&data) + it.offset, getData(it.key), it.size);
}

Это reinterpret_cast выглядит немногонекрасиво, но это просто смещает указатель &data на нужное поле.

Мы также можем создать более умный контейнер, который принимает указатель переменной при построении, таким образом, связывается с существующей переменной и требует небольшой записи:

struct Map2 {
    static constexpr std::size_t max = sizeof(field_keys)/sizeof(*field_keys);

    Map2(data_t* pnt) : mpnt(pnt) {}

    char* getDest(std::size_t num) {
        std::array<char*, max> arr = {
            mpnt->field1,
            mpnt->field2,
            mpnt->field3,
        };
        return arr[num];
    }

    const char* getKey(std::size_t num) {
        return field_keys[num];
    }

    std::size_t getSize(std::size_t num) {
        std::array<std::size_t, max> arr = {
            sizeof(mpnt->field1),
            sizeof(mpnt->field2),
            sizeof(mpnt->field3),
        };
        return arr[num];
    }

private:
    data_t* mpnt;
};

Но, вероятно, делает итерацию более читабельной:

Map2 m(&data);
for (std::size_t i = 0; i < m.max; ++i) {
    mystrlcpy(m.getDest(i), getData(m.getKey(i)), m.getSize(i));
}

Живой код доступен на onlinegdb .

0 голосов
/ 23 октября 2018

Отображение может быть функцией для записи данных в соответствующий элемент

struct mapping_t
{
    const char * name;
    std::function<void(my_struct_t *, const char *)> write;
};

const std::vector<mapping_t> mapping = {
    { "FIRST_KEY",  [](data_t & data, const char * str) { strlcpy(data.field1, str, sizeof(data.field1); } }
    { "SECOND_KEY", [](data_t & data, const char * str) { strlcpy(data.field2, str, sizeof(data.field2); } },
    { "THIRD_KEY",  [](data_t & data, const char * str) { strlcpy(data.field3, str, sizeof(data.field3); } },
};

int main()
{
    data_t data;

    for (auto const& it : mapping) {
        it.write(data, getData(it.name));
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...