【问题标题】:Is Pointer-to- " inner struct" member forbidden?是否禁止指向“内部结构”成员?
【发布时间】:2009-12-18 18:20:29
【问题描述】:

我有一个嵌套结构,我想要一个指向嵌套成员之一的成员指针:

合法吗?

struct InnerStruct
{
    bool c;
};
struct MyStruct {
    bool t;
    bool b;
    InnerStruct inner;
}; 

这个:

MyStruct mystruct;
//...
bool MyStruct::* toto = &MyStruct::b;

没问题,但是:

bool MyStruct::* toto = &MyStruct::inner.c;

不是。有什么想法吗?

谢谢

这里有一些细节 是的,它是 &MyStruct::b 而不是 mystruct::b; 该代码来自自定义 RTTI/Property 系统。 对于每个指定的类,我们保留一个“属性”数组,包括一个 Ptr-to-member 它是这样使用的:

//somewhere else in code...
( myBaseClassWithCustomRTTIPointer)->* toto = true;

【问题讨论】:

  • 我很困惑:你的意思是在你的引用中指的是 MyStruct::b 和 MyStruct::inner.c 与 mystruct.b 和 mystruct.inner.c 吗?您在两个作业中指定 MyStruct::* 的目标是什么?
  • 我认为他想做(mystruct.*(&MyStruct::inner.c)) = true 并让它与mystruct.inner.c = true 相同。
  • @Joe:C++ 中指向数据成员的指针只不过是“偏移”概念的语言级实现。 IE。在内部,指向数据成员的指针只不过是成员相对于整个对象开头的偏移量。显然,子对象的成员在整个对象中也有自己的偏移量,因此允许 bool MyStruct::* 类型的指针指向 MyStruct::InnerStruct 成员是完全合乎逻辑的,就像在 OP 的示例中一样。但是,语言不允许这样做。根本没有语法。
  • 感谢大家快速准确的回答。我认为 ptr-to-member 比仅仅从基类偏移要复杂一些,因为它可以处理多重继承和虚拟继承。我可能会更改我的属性系统,使其使用 offsetof 并禁止“可怕的钻石”
  • @benoitj:不,你把事情复杂化了。只有指向成员函数的指针才会出现多重继承、虚拟继承和各种“钻石”的问题。使用指向数据成员的指针不存在这样的复杂性。指向数据成员的指针要简单得多。是的,指向数据成员 is 的指针只是一个偏移量(在典型实现中)。

标签: c++ pointers


【解决方案1】:

是的,这是被禁止的。你不是第一个提出这个完全合乎逻辑的想法的人。在我看来,这是 C++ 中成员指针规范中明显的“错误”/“遗漏”之一,但显然委员会对进一步开发成员指针规范没有兴趣(因为大多数“低级”语言功能的情况)。

请注意,在语言中实现该功能所需的一切。指向成员数据成员的指针与指向直接数据成员的指针没有任何不同。唯一缺少的是初始化这样一个指针的语法。然而,委员会显然对引入这样的语法不感兴趣。

从纯形式逻辑的角度来看,这在 C++ 中应该是允许的

struct Inner {
  int i;
  int j[10];
};

struct Outer {
  int i;
  int j[10];
  Inner inner;
};

Outer o;
int Outer::*p;

p = &Outer::i; // OK
o.*p = 0; // sets `o.i` to 0

p = &Outer::inner.i; // ERROR, but should have been supported
o.*p = 0; // sets `o.inner.i` to 0

p = &Outer::j[0]; // ERROR, but should have been supported
o.*p = 0; // sets `o.j[0]` to 0
// This could have been used to implement something akin to "array type decay" 
// for member pointers

p = &Outer::j[3]; // ERROR, but should have been supported
o.*p = 0; // sets `o.j[3]` to 0

p = &Outer::inner.j[5]; // ERROR, but should have been supported
o.*p = 0; // sets `o.inner.j[5]` to 0

pointer-to-data-member 的典型实现只不过是成员从封闭对象开始的字节偏移量。由于所有成员(立即数和成员的成员)最终在内存中都是按顺序排列的,因此成员的成员也可以通过特定的偏移值来标识。这就是我说这个功能的内部工作已经完全实现的意思,所需要的只是初始化语法。

在 C 语言中,此功能由通过标准 offsetof 宏获得的显式偏移来模拟。在 C 中我可以获得offsetof(Outer, inner.i)offsetof(Outer, j[2])。不幸的是,这种能力并未反映在 C++ 指向数据成员的指针中。

【讨论】:

  • 在 C++0x 中,我们可以使用 sizeof(Outer::inner.i)decltype(Outer::inner.i)。我想知道是什么原因使指向此类子元素的成员指针更加复杂?
  • 关于数组指针的例子,你可以这样做:Outer o; int (Outer::*p_array)[10]; p_array = &Outer::j; o.*p_array[3] = 0;
  • @Grim Fandango:是的,但这恰好不是重点。我们谈论的是指向进入子对象的能力。
【解决方案2】:

您关心的 InnerStruct 恰好包含在 MyStruct 的一个实例中,但这不会影响您如何获取指向 InnerStruct 成员的指针。

bool InnerStruct::* toto2 = &InnerStruct::c;

编辑:重读你的问题,我猜你想定义一个指向外部结构成员的指针,并让它直接指向内部结构的成员。这根本是不允许的。要获取包含在外部结构中的内部结构的成员,您必须创建一个指向内部结构本身的指针,然后再创建指向其成员的指针。要使用它,您需要取消对成员的两个指针的引用:

// Pointer to inner member of MyStruct:
InnerStruct MyStruct::* toto = &MyStruct::inner;

// Pointer to c member of InnerStruct:
bool InnerStruct::* toto2 = &InnerStruct::c;

// Dereference both to get to the actual bool:
bool x = mystruct.*toto.*toto2;

【讨论】:

  • @JerryCoffin:感谢您的出色回答。您将如何使用这种表示法来引用 struct Outer { int i; 中的 j[3] 元素?诠释 j[5]; }
  • @GrimFandango:您将创建指向成员的指针,例如:int (Outer::* ptr_j)[5] = &Outer::j;。假设Outer 中的某个实例x,您将取消对它的引用:(x.*ptr_j)[3] = 1;
【解决方案3】:

正如 AnT 的回答中所述,这似乎确实是对标准的遗漏,并且缺乏正确的语法来表达您想要做的事情。我的一位同事前几天遇到了这个问题,并引用了您的问题及其答案作为无法完成的证据。嗯,我喜欢挑战,是的,它可以完成......但它并不漂亮。

首先,重要的是要认识到指向成员的指针基本上是指向 struct[1] 的指针的偏移量。该语言确实有一个与此相似的 offsetof 运算符,有趣的是,它为我们提供了做我们想做的事情所需的表达能力。

我们立即遇到的问题是 C++ 禁止强制转换成员指针。好吧,几乎……我们让工会为此袖手旁观。正如我所说,这不漂亮!

最后,我们还需要知道要转换为的正确指针类型。

所以事不宜迟,下面是代码(在 gcc 和 clang 上测试):

template <typename C, typename T, /*auto*/size_t P>
union MemberPointerImpl final {
  template <typename U> struct Helper
    { using Type = U C::*; };
  template <typename U> struct Helper<U&>
    { using Type = U C::*; };

  using MemberPointer = typename Helper<T>::Type;

  MemberPointer o;
  size_t i = P; // we can't do "auto i" - argh!
  static_assert(sizeof(i) == sizeof(o));
};

#define MEMBER_POINTER(C, M) \
  ((MemberPointerImpl<__typeof__(C),          \
      decltype(((__typeof__(C)*)nullptr)->M), \
      __builtin_offsetof(__typeof__(C), M)    \
  >{ }).o)

我们先看一下宏MEMBER_POINTER。它需要两个参数。第一个 C 是结构体,它将成为成员指针的基础。将其包装在 __typeof__ 中并不是绝对必要的,但允许传递类型或变量。第二个参数M 为我们想要指针的成员提供了一个表达式。

MEMBER_POINTER 宏从这些参数中提取两条额外的信息,并将它们作为参数传递给MemberPointerImpl 模板联合。第一部分是指向的成员的类型。这是通过使用空指针构造一个表达式来完成的,我们在其上使用decltype。第二部分是从基本结构到相关成员的偏移量。

MemberPointerImpl 中,我们需要构造MemberPointer 类型,这将是宏返回的内容。这是通过一个辅助结构来完成的,如果我们的成员是一个数组元素,它会删除无用的引用,也允许对此提供支持。如果我们将返回值分配给类型不匹配的变量,它还使 gcc 和 clang 能够在诊断中为我们提供一个很好的完全扩展类型。

所以,要使用MEMBER_POINTER,只需将您的代码更改为:

bool MyStruct::* toto = &MyStruct::inner.c;

到:

bool MyStruct::* toto = MEMBER_POINTER(MyStruct, inner.c);

[1] 好的,请注意:这可能不适用于所有架构/编译器,因此可移植的代码编写者现在把目光移开!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-06-23
    • 1970-01-01
    • 2011-11-10
    • 2021-07-26
    • 2023-03-12
    • 1970-01-01
    • 2014-06-09
    • 1970-01-01
    相关资源
    最近更新 更多