使用 offsetof() 从成员变量中获取所有者对象
Using offsetof() to get owner object from member variable
我想在这里实现'GetParent()'功能-
class ChildClass;
class ParentClass
{
public:
....
ChildClass childObj;
....
};
class ChildClass
{
friend class ParentClass;
private:
ChildClass();
public:
ParentClass* GetParent();
};
我试图创建一个私有成员变量来存储指向父对象的指针。
但是这种方法需要额外的内存。
class ChildClass
{
friend class ParentClass;
private:
ChildClass();
ParentClass* m_parent;
public:
ParentClass* GetParent()
{
return m_parent;
}
};
所以我使用了 offsetof() 宏(调用 offsetof() 的性能成本可以忽略),但我不确定这种方法是否安全。它适用于所有情况吗?还有更好的idea吗?
class ChildClass
{
public:
ParentClass* GetParent()
{
return reinterpret_cast<ParentClass*>(
reinterpret_cast<int8_t*>(this) - offsetof(ParentClass, childObj)
);
}
};
使用offsetof
计算容器对象的地址是安全的,因为它可以工作。 offsetof
通常在 C 语言中用于此目的。例如,参见 Linux 内核中的 container_of 宏。
如果有一个 ChildClass
实例 而不是 那个特定的成员变量,那么它可能是不安全的,那么你手上就有未定义的行为。当然,由于构造函数是私有的,您应该能够防止这种情况发生。
另一个不安全的原因是它 has undefined behaviour if the container type is not a standard layout type.
所以,只要您考虑到注意事项,它就可以工作。但是,您的实施已损坏。 offsetof
宏的第二个参数必须是成员的名称。在这种情况下,它必须是 childObj
而不是 e[index]
这不是成员的名字。
另外(如果我错了,也许有人会纠正我,但我认为)在进行指针运算之前转换为不相关的类型 uint8_t*
,然后再转换为另一个不相关的类型似乎有点危险。我建议使用 char*
作为中间类型。保证 sizeof(char) == 1
并且它有关于别名和没有陷阱表示的特殊例外。
可能值得一提的是,根据标准,指针算法的这种使用(或除数组使用之外的任何使用)是 not defined。严格来说,这使得 offsetof
毫无用处。不过,指针 被 广泛用于数组之外,因此在这种情况下可以忽略缺乏标准支持。
这里是为未来访客提供的更通用的解决方案:
#include <cstddef>
#include <type_traits>
template <class Struct, std::size_t offset, class Member>
Struct &get_parent_struct_tmpl(Member &m){
static_assert(std::is_standard_layout<Struct>::value,
"Given struct must have a standard layout type");
return *reinterpret_cast<Struct *>(reinterpret_cast<char *>(&m) - offset);
}
#define get_parent_struct(STRUCTNAME, MEMBERNAME, MEMBERREF)\
get_parent_struct_tmpl<STRUCTNAME, offsetof(STRUCTNAME, MEMBERNAME)>(MEMBERREF)
测试用例:
#include <cassert>
struct Foo{
double d;
int i;
bool b;
char c;
bool b2;
};
int main(){
Foo f;
bool &br = f.b;
Foo &fr = get_parent_struct(Foo, b, br);
assert(&fr == &f);
}
有一个 static_assert
可以防御由于给定结构没有 standard layout as mentioned by .
而导致的 UB
显示的代码需要 C++11,但是,您可以删除 #include <type_traits>
和 static_assert
以使其在 C++03 中编译,但是,您必须手动制作确保您有标准布局类型。
我想在这里实现'GetParent()'功能-
class ChildClass;
class ParentClass
{
public:
....
ChildClass childObj;
....
};
class ChildClass
{
friend class ParentClass;
private:
ChildClass();
public:
ParentClass* GetParent();
};
我试图创建一个私有成员变量来存储指向父对象的指针。 但是这种方法需要额外的内存。
class ChildClass
{
friend class ParentClass;
private:
ChildClass();
ParentClass* m_parent;
public:
ParentClass* GetParent()
{
return m_parent;
}
};
所以我使用了 offsetof() 宏(调用 offsetof() 的性能成本可以忽略),但我不确定这种方法是否安全。它适用于所有情况吗?还有更好的idea吗?
class ChildClass
{
public:
ParentClass* GetParent()
{
return reinterpret_cast<ParentClass*>(
reinterpret_cast<int8_t*>(this) - offsetof(ParentClass, childObj)
);
}
};
使用offsetof
计算容器对象的地址是安全的,因为它可以工作。 offsetof
通常在 C 语言中用于此目的。例如,参见 Linux 内核中的 container_of 宏。
如果有一个 ChildClass
实例 而不是 那个特定的成员变量,那么它可能是不安全的,那么你手上就有未定义的行为。当然,由于构造函数是私有的,您应该能够防止这种情况发生。
另一个不安全的原因是它 has undefined behaviour if the container type is not a standard layout type.
所以,只要您考虑到注意事项,它就可以工作。但是,您的实施已损坏。 offsetof
宏的第二个参数必须是成员的名称。在这种情况下,它必须是 childObj
而不是 e[index]
这不是成员的名字。
另外(如果我错了,也许有人会纠正我,但我认为)在进行指针运算之前转换为不相关的类型 uint8_t*
,然后再转换为另一个不相关的类型似乎有点危险。我建议使用 char*
作为中间类型。保证 sizeof(char) == 1
并且它有关于别名和没有陷阱表示的特殊例外。
可能值得一提的是,根据标准,指针算法的这种使用(或除数组使用之外的任何使用)是 not defined。严格来说,这使得 offsetof
毫无用处。不过,指针 被 广泛用于数组之外,因此在这种情况下可以忽略缺乏标准支持。
这里是为未来访客提供的更通用的解决方案:
#include <cstddef>
#include <type_traits>
template <class Struct, std::size_t offset, class Member>
Struct &get_parent_struct_tmpl(Member &m){
static_assert(std::is_standard_layout<Struct>::value,
"Given struct must have a standard layout type");
return *reinterpret_cast<Struct *>(reinterpret_cast<char *>(&m) - offset);
}
#define get_parent_struct(STRUCTNAME, MEMBERNAME, MEMBERREF)\
get_parent_struct_tmpl<STRUCTNAME, offsetof(STRUCTNAME, MEMBERNAME)>(MEMBERREF)
测试用例:
#include <cassert>
struct Foo{
double d;
int i;
bool b;
char c;
bool b2;
};
int main(){
Foo f;
bool &br = f.b;
Foo &fr = get_parent_struct(Foo, b, br);
assert(&fr == &f);
}
有一个 static_assert
可以防御由于给定结构没有 standard layout as mentioned by
显示的代码需要 C++11,但是,您可以删除 #include <type_traits>
和 static_assert
以使其在 C++03 中编译,但是,您必须手动制作确保您有标准布局类型。