引用嵌套成员的安全方法
Safe way to reference nested member
我有一个 struct
和其他 struct
成员。外部和内部结构都是 StandardLayout
(甚至可以假设内部是普通的旧数据)。像这样:
struct Inner1 {
int a = 0, b = 0;
};
struct Inner2 {
int c = 0, d = 0;
};
struct Outer {
Inner1 x;
std::string s;
Inner2 y;
};
我想编写一些函数,它接受 Outer&
和某种类型的对象 T
,可以 return 任何嵌套字段的值,具体取决于参数:
int get(Outer& o, T field);
如果 Outer
是一个平面结构,指向成员的指针正是我所需要的,但它不是平面的。
简单的方法是将所有字段的 T
设为 enum
并写成 switch
,但我觉得效率不高。更快的方法是使 T
成为偏移量并写入
int get(Outer& o, size_t field) {
return *reinterpret_cast<int*>(reinterpret_cast<char*>(o) + field);
}
然后像 get(o, offsetof(Outer, y) + offsetof(Inner2, c))
一样称呼它。它有效,但我不确定它是否保证有效 - 像这样对偏移量求和是否正确以及通过偏移量获取成员值是否安全。
所以,问题是:这种方式安全吗?如果没有,有安全的方法吗? T
的构造值可以是任意复杂的,但是使用它们应该很快。
动机:我需要将某些嵌套字段中的值按某种顺序放置,这些顺序在启动时已知,但在编译时未知。我想在启动时创建一个 T
的数组,然后在获取特定对象时使用这个预先计算的数组。
[UPD]: 所以会这样使用:
void bar(int);
void foo(Outer& o, vector<T>& fields) {
for (auto& field : fields) {
bar(get(o, field));
}
}
我确实认为这是安全的(因为没有违反严格的别名)。
但是,该语言确实有更好的机制来执行此操作:指向数据成员的指针,它基本上可以编译成一个偏移量。
需要注意的是,您必须为 Inner1
和 Inner2
分别重载
int get(Outer& o, int Inner1::* m) {
return o.x.*m;
}
int get(Outer& o, int Inner2::* m) {
return o.y.*m;
}
int foo() {
Outer tmp;
return get(tmp, &Inner1::a) + get(tmp, &Inner2::d);
}
您可以使用函数模板专业化实现相同的效果,请参阅下面的示例代码
#include <iostream>
using namespace std;
struct Inner1 {
int a = 1, b = 2;
};
struct Inner2 {
int c = 3, d = 4;
};
struct Outer {
Inner1 x;
std::string s;
Inner2 y;
};
template<typename T>
int get(Outer&o);
template<>
int get<Inner1>(Outer& o)
{
return o.x.a;
}
template<>
int get<Inner2>(Outer& o)
{
return o.y.c;
}
int main()
{
Outer out;
std::cout << get<Inner1>(out) << std::endl;
std::cout << get<Inner2>(out) << std::endl;
return 0;
}
希望对您有所帮助!更有趣的是,这是类型安全的。
你可以这样做。
/* main.cpp */
#include <string>
#include <iostream>
using namespace std;
struct Inner1 {
int a = 0, b = 0;
};
struct Inner2 {
int c = 0, d = 0;
};
struct Outer {
Inner1 x;
std::string s;
Inner2 y;
};
struct OuterMember
{
int (*getter)(Outer &obj);
};
inline int get(Outer &obj,OuterMember field) { return field.getter(obj); }
template <auto Ptr1,auto Ptr2>
auto GetInnerMember(Outer &obj) { return (obj.*Ptr1).*Ptr2; }
inline constexpr OuterMember OuterMemberA = { GetInnerMember<&Outer::x,&Inner1::a> } ;
inline constexpr OuterMember OuterMemberB = { GetInnerMember<&Outer::x,&Inner1::b> } ;
inline constexpr OuterMember OuterMemberC = { GetInnerMember<&Outer::y,&Inner2::c> } ;
inline constexpr OuterMember OuterMemberD = { GetInnerMember<&Outer::y,&Inner2::d> } ;
/* main() */
int main()
{
Outer obj;
obj.x.a=1;
obj.x.b=2;
obj.y.c=3;
obj.y.d=4;
cout << "a = " << get(obj,OuterMemberA) << endl ;
cout << "b = " << get(obj,OuterMemberB) << endl ;
cout << "c = " << get(obj,OuterMemberC) << endl ;
cout << "d = " << get(obj,OuterMemberD) << endl ;
return 0;
}
我有一个 struct
和其他 struct
成员。外部和内部结构都是 StandardLayout
(甚至可以假设内部是普通的旧数据)。像这样:
struct Inner1 {
int a = 0, b = 0;
};
struct Inner2 {
int c = 0, d = 0;
};
struct Outer {
Inner1 x;
std::string s;
Inner2 y;
};
我想编写一些函数,它接受 Outer&
和某种类型的对象 T
,可以 return 任何嵌套字段的值,具体取决于参数:
int get(Outer& o, T field);
如果 Outer
是一个平面结构,指向成员的指针正是我所需要的,但它不是平面的。
简单的方法是将所有字段的 T
设为 enum
并写成 switch
,但我觉得效率不高。更快的方法是使 T
成为偏移量并写入
int get(Outer& o, size_t field) {
return *reinterpret_cast<int*>(reinterpret_cast<char*>(o) + field);
}
然后像 get(o, offsetof(Outer, y) + offsetof(Inner2, c))
一样称呼它。它有效,但我不确定它是否保证有效 - 像这样对偏移量求和是否正确以及通过偏移量获取成员值是否安全。
所以,问题是:这种方式安全吗?如果没有,有安全的方法吗? T
的构造值可以是任意复杂的,但是使用它们应该很快。
动机:我需要将某些嵌套字段中的值按某种顺序放置,这些顺序在启动时已知,但在编译时未知。我想在启动时创建一个 T
的数组,然后在获取特定对象时使用这个预先计算的数组。
[UPD]: 所以会这样使用:
void bar(int);
void foo(Outer& o, vector<T>& fields) {
for (auto& field : fields) {
bar(get(o, field));
}
}
我确实认为这是安全的(因为没有违反严格的别名)。
但是,该语言确实有更好的机制来执行此操作:指向数据成员的指针,它基本上可以编译成一个偏移量。
需要注意的是,您必须为 Inner1
和 Inner2
int get(Outer& o, int Inner1::* m) {
return o.x.*m;
}
int get(Outer& o, int Inner2::* m) {
return o.y.*m;
}
int foo() {
Outer tmp;
return get(tmp, &Inner1::a) + get(tmp, &Inner2::d);
}
您可以使用函数模板专业化实现相同的效果,请参阅下面的示例代码
#include <iostream>
using namespace std;
struct Inner1 {
int a = 1, b = 2;
};
struct Inner2 {
int c = 3, d = 4;
};
struct Outer {
Inner1 x;
std::string s;
Inner2 y;
};
template<typename T>
int get(Outer&o);
template<>
int get<Inner1>(Outer& o)
{
return o.x.a;
}
template<>
int get<Inner2>(Outer& o)
{
return o.y.c;
}
int main()
{
Outer out;
std::cout << get<Inner1>(out) << std::endl;
std::cout << get<Inner2>(out) << std::endl;
return 0;
}
希望对您有所帮助!更有趣的是,这是类型安全的。
你可以这样做。
/* main.cpp */
#include <string>
#include <iostream>
using namespace std;
struct Inner1 {
int a = 0, b = 0;
};
struct Inner2 {
int c = 0, d = 0;
};
struct Outer {
Inner1 x;
std::string s;
Inner2 y;
};
struct OuterMember
{
int (*getter)(Outer &obj);
};
inline int get(Outer &obj,OuterMember field) { return field.getter(obj); }
template <auto Ptr1,auto Ptr2>
auto GetInnerMember(Outer &obj) { return (obj.*Ptr1).*Ptr2; }
inline constexpr OuterMember OuterMemberA = { GetInnerMember<&Outer::x,&Inner1::a> } ;
inline constexpr OuterMember OuterMemberB = { GetInnerMember<&Outer::x,&Inner1::b> } ;
inline constexpr OuterMember OuterMemberC = { GetInnerMember<&Outer::y,&Inner2::c> } ;
inline constexpr OuterMember OuterMemberD = { GetInnerMember<&Outer::y,&Inner2::d> } ;
/* main() */
int main()
{
Outer obj;
obj.x.a=1;
obj.x.b=2;
obj.y.c=3;
obj.y.d=4;
cout << "a = " << get(obj,OuterMemberA) << endl ;
cout << "b = " << get(obj,OuterMemberB) << endl ;
cout << "c = " << get(obj,OuterMemberC) << endl ;
cout << "d = " << get(obj,OuterMemberD) << endl ;
return 0;
}