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

时间:2015-12-29 13:05:34

标签: c++ pointers member offsetof

我想在这里实现'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)
            );
    }
};

2 个答案:

答案 0 :(得分:2)

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

在某种意义上,如果某个ChildClass实例该特定成员变量,则可能是不安全的,那么您手上就有未定义的行为。当然,由于构造函数是私有的,你应该能够阻止它。

如果容器类型不是has undefined behaviour,那么它不安全的另一个原因是standard layout type

因此,只要您考虑到警告,它就可以工作。但是,您的实施已被破坏。 offsetof宏的第二个参数必须是成员的名称。在这种情况下,它必须是childObj而不是e[index],而不是会员的名称。

另外(也许有人会纠正我,如果我错了,但我认为)在做指针算术之前投射到不相关的类型uint8_t*然后再投射到另一个不相关的类型似乎有点危险。我建议使用char*作为中间类型。保证sizeof(char) == 1并且它有关于别名和没有陷阱表示的特殊例外。

值得一提的是,标准使用指针算法的这种用法 - 或者除了用于数组之外的任何用途 - 都是not defined。严格来说,offsetof毫无用处。仍然,指针 在数组之外被广泛使用,因此在这种情况下可以忽略标准支持的缺乏。

答案 1 :(得分:2)

这是针对未来访问者的更通用的解决方案:

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

standard提到的给定结构没有layout user2079303导致对{U}进行防御{/ 1}}。

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