【问题标题】:C++: Get adress of complete object containing member subobjectC ++:获取包含成员子对象的完整对象的地址
【发布时间】:2018-05-28 12:48:52
【问题描述】:

我有两个类,大致定义如下:

class Inner {
public:
  bool is_first;
};

class Outer {
public:
  char some_other_member;
  Inner first;
  Inner second;
}

我知道Inners 只存在于Outers 中,并且当且仅当相应的对象是first 成员而不是@987654327 时,相应的布尔标志才会设置为true @。

我正在寻找一种符合标准的方法来派生指向包含一些 Inner 对象的 Outer 对象的指针。当然,我可以在每个 Inner 对象中存储一个指针,但由于 Inner 类非常小,而且我有 很多 它们,这似乎是浪费内存(因此很宝贵缓存)。

显然,编译器应该知道firstsecond 和包含Outer 对象之间的内存偏移量。问题是:是否有一种符合标准的方式告诉编译器“获取该偏移量,从指向Inner 的指针中减去它并使其成为Outer 指针”?

我知道如果Outer 将包含Inners 作为基本子对象(例如this),我可以使用转换为void - 我非常认为成员子对象应该可以进行类似的操作?

【问题讨论】:

  • 如果只有first second 将同时有效,为什么会有两个成员呢?导致这样设计的真实实际问题是什么?不能通过继承和多态来解决吗?
  • 不,两者同时有效。两个内部对象实际上是两个红黑树节点,代表一个区间的开始和结束(也就是外部类)。问题是:他们知道他们是first还是second
  • 我希望您建议您应该开始 C++ 编程。像你计划的那样做指针算术是不好的做法。你没有计算指针,你也改变了类型。从 OOP 的角度来看,您将课堂上的所有知识都放在外面的“某物”中。为什么不简单地从指向成员的实例返回一个指针呢?这将保留类型并将所有信息保留在内部。顺便说一句:为什么你需要一个外部!指向对象内部的指针。所有这些都是糟糕的设计!
  • @LukasBarth:我相信你走错了路!如果您相信,手动减去指针会比返回指向子对象的指针更聪明,那么您就错了。如果您返回一个指向子对象的指针,编译器将简单地创建一个方法,将已知的常量偏移量添加到 this 指针。正是你需要的。所以我不明白你的好处是什么。首先尝试使用 OOP 设计在 c++ 中对其进行编码。如果遇到性能问题,请转到生成的程序集。如果您相信,您的手工代码可以更好,您可以选择优化。
  • @LukasBarth:问题始于一般设计:为什么您需要为存储在已知元素中的东西设置标志。它作为第一个或第二个元素存储的知识已经可用。接下来是,你有一个对象,它只能在另一个对象中存活,但你想处理指向内部对象的指针。所有这一切都让我觉得有一些逻辑颠倒。也许重新考虑潜在问题是一个想法?

标签: c++ c++14


【解决方案1】:

你应该注意,从一个指向成员的指针中获取指向父对象的指针的问题一般不可解决。

offsetof 和强制转换仅适用于 standard layout types。在其他情况下,它是未定义的行为。

例如,对于多重虚拟继承,它会失败。在这种情况下,成员访问是通过比添加编译时间偏移更复杂的方式实现的。也许这实际上并不那么重要,因为它很少使用,但您明确要求符合标准。

【讨论】:

  • 谢谢,我害怕那个。不幸的是,我的Outer 确实使用了继承,并且在其继承层次结构的多个类中具有非静态数据成员。我仍然觉得在这种情况下编译器计算偏移量应该不是问题 - 但我不想进入 UB-land。
  • 你可以在你的类或get_outer 函数中做一个静态断言来检查标准布局,例如static_assert( std::is_standard_layout<Outer>::value, "Outer must be standard layout"); 那么你就安全了,远离 UB-land :-) (offsetof 并且保证铸造适用于标准布局类型)。实际上offsetof 也适用于许多非标准布局类型,但这是 UB。
  • 从 C++17 开始,它是“有条件地定义”的,而不是标准布局类型。它似乎没有说明它是在什么条件下定义的。
【解决方案2】:

没有一个解决方案是完全正确的,你与pointer arithmetic rules 发生了冲突。但是,有一个实现定义的解决方案,它几乎总是按照您的期望进行

auto get_outer(Inner& i)
{
    return (Inner*)(((uintptr_t)&i) - offsetof(Outer, first));
}

catch 是指针和整数类型转换是实现定义的。

【讨论】:

【解决方案3】:

这就是offsetof 的用途。

类似:

Outer *Inner::getOuter() {
    size_t offset = flag ? offsetof(Outer, first) : offsetof(Outer, second);
    return reinterpret_cast<Outer *>(
        reinterpret_cast<char *>(this) - offset);
}

请注意,您是 reinterpret_casting,因此您有责任知道自己在做什么。如果标志不同步,您将获得未定义的行为。

请注意,指针运算只允许在数组中使用,但 §6.9/4 ([basic.types]) 明确指出每个对象都存储在 unsigned char 这样的数组中:

T 类型对象的 对象表示T 类型对象占用的 Nunsigned char 对象的序列,其中 N 等于sizeof(T)

在这个数组中,定义了指针算法。但你需要确保你确实在分配范围内。

【讨论】:

  • 这确实有路人在他的回答中提到的问题,对吧?本质上:从char * 表达式中减去只有在该表达式确实指向char 数组时才是严格定义的行为? 编辑:标准链接:eel.is/c++draft/expr.add#4
  • @LukasBarth,不,char * 被明确允许为任何东西起别名,所以只要您留在分配范围内,这个 已定义
  • @LukasBarth,事实上,路人的答案是正确的(正如一个非常真实且曾经非常常见的架构所证明的那样,根据编译器设置,指针添加是比整数加法更复杂)。
猜你喜欢
  • 2013-12-22
  • 1970-01-01
  • 2012-07-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-10-19
相关资源
最近更新 更多