简体   繁体   English

使用struct member指针在C ++中填充结构

[英]Use struct member pointer to fill-in a struct in C++

So I have the following available: 所以我有以下几种:

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

This code is given to my and I cannot modify it in any way. 这段代码是给我的,我不能以任何方式修改它。 It comes from some old C project. 它来自一些旧的C项目。

I need to fill in the struct using the getData function with the different keys, something like the following: 我需要使用带有不同键的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));

Of course, this is a simplification, and more things are going on in each assignment. 当然,这是一种简化,每项任务都会有更多的事情发生。 The point is that I would like to represent the mapping between keys and struct member in a constant structure, and use that to transform the last code in a loop. 关键是我想在一个常量结构中表示键和struct成员之间的映射,并使用它来转换循环中的最后一个代码。 I am looking for something like the following: 我正在寻找以下内容:

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

This, however, has two problems: 然而,这有两个问题:

  1. It does not compile :) But I believe that should be easy to solve. 它不编译:)但我相信应该很容易解决。
  2. I am not sure about how to get the sizeof() argument for using strncpy/strlcpy, instead of strcpy. 我不知道如何获得使用strncpy / strlcpy而不是strcpy的sizeof()参数。 I am using char * as the type of the members, so I am losing the type information about how long each array is. 我使用char *作为成员的类型,所以我丢失了关于每个数组有多长的类型信息。 In the other hand, I am not sure how to use the specific char[T] types of each member, because if each struct member pointer has a different type I don't think I will be able to have them in a std::vector<T> . 另一方面,我不确定如何使用每个成员的特定char[T]类型,因为如果每个结构成员指针具有不同的类型,我认为我不能在std::vector<T>使用它们std::vector<T>

A variadic templates based solution: 基于可变模板的解决方案:

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

As explained in my comment, if you can store enough information to process a field in a mapping, then you can write a function that does the same. 正如我的评论中所解释的,如果您可以存储足够的信息来处理映射中的字段,那么您可以编写一个执行相同操作的函数。

Therefore, write a function to do so, using array references to ensure what you do is safe, eg: 因此,编写一个函数来执行此操作,使用数组引用来确保您所做的是安全的,例如:

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

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

And then simply, instead of your for loop: 然后简单地说,而不是你的for循环:

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

Note that the amount of lines is the same as with a mapping (one per field), so this is not worse than a mapping solution in terms of repetition. 请注意,行的数量与映射(每个字段一个)相同,因此在重复方面,这并不比映射解决方案差。

Now, the advantages: 现在,优点:

  • Easier to understand. 更容易理解。

  • Faster: no memory needed to keep the mapping, more easily optimizable, etc. 更快:不需要内存来保持映射,更容易优化等。

  • Allows you to write different functions for different fields, easily, if needed. 如果需要,允许您轻松地为不同的字段编写不同的函数。


Further, if both of your strings are known at compile-time, you can even do: 此外,如果您的两个字符串在编译时都是已知的,您甚至可以执行以下操作:

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

Which will be always safe, eg: 这将永远是安全的,例如:

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

Which has even more pros: 哪个更有优点:

  • Way faster than any strcpy variant (if the call is done in run-time). 方式比任何更快strcpy变种(如呼叫会在运行时完成)。

  • Guaranteed to be safe at compile-time instead of run-time. 保证在编译时安全而不是运行时。

The mapping can be a function to write the data into the appropriate member 映射可以是将数据写入适当成员的函数

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

Since data_t is a POD structure, you can use offsetof() for this. 由于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)}
};

Then the loop would be: 然后循环将是:

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

I don't think there's any way to get the size of the member similarly. 我认为没有办法让会员的规模相似。 You can subtract the offset of the current member from the next member, but this will include padding bytes. 您可以从下一个成员中减去当前成员的偏移量,但这将包括填充字节。 You'd also have to special-case the last member, subtracting the offset from the size of the structure itself, since there's no next member. 你还必须特殊情况下最后一个成员,从结构本身的大小减去偏移量,因为没有下一个成员。

To iterate over struct member you need: 要迭代struct成员,您需要:

  1. offset / pointer to the beginning of that member 偏移/指向该成员开头的指针
  2. size of that member 该成员的大小

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

once we have that we need strlcpy : 一旦我们有了,我们需要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;
}

After having that, we can just: 在那之后,我们可以:

data_t data;

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

That reinterpret_cast looks a bit ugly, but it just shift &data pointer to the needed field. reinterpret_cast看起来有点难看,但它只是将&data指针移动到所需的字段。

We can also create a smarter container which takes variable pointer on construction, thus is bind with an existing variable and it needs a little bit of writing: 我们还可以创建一个更智能的容器,它在构造上接受变量指针,因此与现有变量绑定,需要一点点编写:

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

But probably makes the iterating more readable: 但可能使迭代更具可读性:

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

Live code available at onlinegdb . onlinegdb提供实时代码。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM