【问题标题】:Using offsetof() to get owner object from member variable使用 offsetof() 从成员变量中获取所有者对象
【发布时间】:2016-04-03 09:02:19
【问题描述】:

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

【问题讨论】:

  • 真正想要达到什么目标?
  • 你不能有一个不完整类型的成员变量ChildClass。换句话说,你有一个循环依赖。
  • 另外,你为什么担心指针浪费内存?您打算拥有多少个ChildClass 实例?为什么你的构造函数是private?请更具体,否则我们无法为您提供帮助。你的问题有点不清楚,这就是为什么投反对票的原因(不是我,我认为这个问题可能有一些潜力)
  • 阅读offsetof 可以使用的类型的限制。你不能在这里使用它。
  • RAM 很便宜。 RAM 价格处于历史低位。

标签: c++ pointers member offsetof


【解决方案1】:

这里为未来的访问者提供了一个更通用的解决方案:

#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 可以防御由user2079303 提到的没有standard layout 的给定结构引起的UB。

显示的代码需要 C++11,但是,您可以删除 #include &lt;type_traits&gt;static_assert 使其在 C++03 中编译,但是,您必须手动确保您具有标准布局输入。

【讨论】:

    【解决方案2】:

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

    从某种意义上说,如果有一个ChildClass 实例不是那个特定的成员变量,那么这可能是不安全的,那么你手上有未定义的行为。当然,由于构造函数是私有的,你应该可以防止这种情况发生。

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

    因此,只要您考虑到注意事项,它就可以工作。但是,您的实现已损坏。 offsetof 宏的第二个参数必须是成员的名称。在这种情况下,它必须是childObj 而不是e[index],它不是成员的名字。

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

    值得一提的是,指针算术的这种使用——或除与数组一起使用之外的任何使用——按照标准是not defined。严格来说,这使得offsetof 毫无用处。尽管如此,指针在数组之外被广泛使用,因此在这种情况下可以忽略缺乏标准支持。

    【讨论】:

      猜你喜欢
      • 2017-10-19
      • 2017-09-13
      • 1970-01-01
      • 1970-01-01
      • 2011-10-12
      • 1970-01-01
      • 2013-01-09
      • 2021-05-10
      • 1970-01-01
      相关资源
      最近更新 更多