使用offsetof()从成员变量中获取所有者对象

Using offsetof() to get owner object from member variable

本文关键字:获取 所有者 对象 变量 成员 offsetof 使用      更新时间:2023-10-16

我想在这里实现"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()的性能成本),但我不确定这种方法是否安全。这对每种情况都有效吗?还有更好的主意吗?

class ChildClass
{
public:
    ParentClass* GetParent()
    {
        return reinterpret_cast<ParentClass*>(
            reinterpret_cast<int8_t*>(this) - offsetof(ParentClass, childObj)
            );
    }
};

使用offsetof计算容器对象的地址是安全的,因为它可以工作。CCD_ 2通常用于C中。例如,请参阅Linux内核中的container_of宏。

这可能是不安全的,因为如果有一个ChildClass实例是而不是该特定成员变量,那么您就有未定义的行为。当然,由于构造函数是私有的,您应该能够防止这种情况发生。

它不安全的另一个原因是,如果容器类型不是标准布局类型,它会有未定义的行为。

因此,只要你考虑到这些注意事项,它就可以起作用。然而,您的实现已经崩溃。offsetof宏的第二个参数必须是成员的名称。在这种情况下,它必须是childObj,而不是不是不是成员名称的e[index]

此外(如果我错了,也许有人会纠正我,但我认为)在进行指针运算之前,先转换为一个不相关的类型uint8_t*,然后再转换为另一个不关联的类型似乎有点危险。我建议使用char*作为中间类型。保证了sizeof(char) == 1和它有关于混叠和不具有陷阱表示的特殊例外。

值得一提的是,指针算术的这种使用——或者除了与数组一起使用之外的任何使用——都不是由标准定义的。严格来说,这使得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可以防御用户2079303提到的给定结构没有标准布局所导致的UB。

如图所示的代码需要C++11,但是,您可以删除#include <type_traits>static_assert,使其在C++03中编译,但是,必须手动确保您具有标准布局类型。