C++中使用struct成员指针填充一个struct
Use struct member pointer to fill-in a struct in C++
所以我有以下可用的:
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>);
}
}
然而,这有两个问题:
- 编译不过:)不过我相信应该很容易解决。
- 我不确定如何获取
sizeof()
参数以使用 strncpy/strlcpy,而不是 strcpy。我使用 char *
作为成员的类型,所以我丢失了关于每个数组有多长的类型信息。另一方面,我不确定如何使用每个成员的特定 char[T]
类型,因为如果每个结构成员指针具有不同的类型,我认为我无法将它们放在 std::vector<T>
.
映射可以是将数据写入适当成员的函数
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));
}
}
因为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));
}
我认为没有任何方法可以类似地获取成员的大小。您可以从下一个成员中减去当前成员的偏移量,但这将包括填充字节。您还必须对最后一个成员进行特殊处理,从结构本身的大小中减去偏移量,因为没有下一个成员。
要遍历您需要的结构成员:
- 指向该成员开头的偏移量/指针
- 该成员的大小
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 = '[=11=]';
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));
}
提供实时代码
基于可变参数模板的解决方案:
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;
}
正如我在评论中所解释的那样,如果您可以存储足够的信息来处理映射中的字段,那么您可以编写一个函数来执行相同的操作。
因此,写一个函数来这样做,使用数组引用来确保你所做的是安全的,例如:
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
哪个更有优点:
Way 比任何 strcpy
变体都快(如果调用在 运行 时间内完成)。
保证在编译时安全而不是运行-time。
所以我有以下可用的:
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>);
}
}
然而,这有两个问题:
- 编译不过:)不过我相信应该很容易解决。
- 我不确定如何获取
sizeof()
参数以使用 strncpy/strlcpy,而不是 strcpy。我使用char *
作为成员的类型,所以我丢失了关于每个数组有多长的类型信息。另一方面,我不确定如何使用每个成员的特定char[T]
类型,因为如果每个结构成员指针具有不同的类型,我认为我无法将它们放在std::vector<T>
.
映射可以是将数据写入适当成员的函数
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));
}
}
因为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));
}
我认为没有任何方法可以类似地获取成员的大小。您可以从下一个成员中减去当前成员的偏移量,但这将包括填充字节。您还必须对最后一个成员进行特殊处理,从结构本身的大小中减去偏移量,因为没有下一个成员。
要遍历您需要的结构成员:
- 指向该成员开头的偏移量/指针
- 该成员的大小
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 = '[=11=]';
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));
}
提供实时代码
基于可变参数模板的解决方案:
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;
}
正如我在评论中所解释的那样,如果您可以存储足够的信息来处理映射中的字段,那么您可以编写一个函数来执行相同的操作。
因此,写一个函数来这样做,使用数组引用来确保你所做的是安全的,例如:
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
哪个更有优点:
Way 比任何
strcpy
变体都快(如果调用在 运行 时间内完成)。保证在编译时安全而不是运行-time。