什么是`int foo :: * bar :: *`?

时间:2014-10-05 07:15:55

标签: c++ pointer-to-member

C ++很酷的一点是,它允许您创建指向成员类型的变量。最常见的用例似乎是获取指向方法的指针:

struct foo
{
    int x() { return 5; }
};

int (foo::*ptr)() = &foo::x;
foo myFoo;
cout << (myFoo.*ptr)() << '\n'; // prints "5"

然而,乱搞,我意识到他们也可以指向成员变量:

struct foo
{
    int y;
};

int foo::*ptr = &foo::y;
foo myFoo;
myFoo.*ptr = 5;
cout << myFoo.y << '\n'; // prints "5"

这很漂亮。它引导我进一步的实验:如果你能得到一个指向结构子成员的指针怎么办?

struct foo
{
    int y;
};

struct bar
{
    foo aFoo;
};

int bar::*foo::*ptr;

actually compiles

但是,我不知道如何分配任何有用的东西。以下都不起作用:

int bar::*foo::*ptr = &bar::foo::y; // no member named "foo" in "bar"
int bar::*foo::*ptr = &bar::aFoo::y; // no member named "aFoo" in "bar" (??)
int bar::*foo::*ptr = &foo::y; // can't init 'int bar::*foo::*' with 'int foo::*'

此外,根据产生的错误,似乎这种类型并不是我想到的:

int bar::*foo::*ptr = nullptr;
bar myBar;
myBar.*ptr = 4; // pointer to member type ‘int bar::*’ incompatible
                // with object type ‘bar’

这个概念似乎在逃避我。显然,我不能排除它只是以一种完全不同于我期望的方式进行解析。

有人请您解释一下int bar::*foo::*实际上是什么吗?为什么gcc告诉我指向bar成员的指针与bar对象不兼容?我如何使用int bar::*foo::*,我将如何构建有效的?{/ p>

5 个答案:

答案 0 :(得分:54)

这是初始化这种怪物的“有效”方式:

struct bar;

struct foo
{
    int y;    
    int bar::* whatever;
};

struct bar
{
    foo aFoo;
};

int bar::* foo::* ptr = &foo::whatever;

正如我们所看到的,ptr是指向foofoo::*的成员,从右向左阅读)的指针,其中该成员本身是指向{的成员的指针{1}}(bar),其中该成员是int。

  

我如何使用int bar :: * foo :: *

你不会,希望如此!但如果你受到胁迫,试试这个!

bar::*

答案 1 :(得分:19)

这是指向数据成员的指针,数据成员本身是指向数据成员(int的{​​{1}}成员)的指针。

不要问我它实际上有用的是什么 - 我的头旋转了一点:)

编辑:这是一个完整的实例:

bar

输出当然是42。

它可以变得更有趣 - 这里有一些指向成员函数的指针,它们返回指向成员函数的指针:

#include <iostream>

struct bar {
    int i;
};

struct foo {
    int bar::* p;
};

int main()
{
    bar b;
    b.i = 42;

    foo f;
    f.p = &bar::i;

    int bar::*foo::*ptr = &foo::p;
    std::cout << (b.*(f.*ptr));
}

答案 2 :(得分:13)

让我们解析声明int bar::*foo::*ptr;

§8.3.3[dcl.mptr] / p1:

  

在声明T DD的格式为

nested-name-specifier * attribute-specifier-seq_opt cv-qualifier-seq_opt D1
     

和nested-name-specifier表示一个类,以及它的类型   声明T D1中的标识符是   “ derived-declarator-type-list T”,然后是标识符的类型   D的“ derived-declarator-type-list cv-qualifier-seq 指针   到类T的类 nested-name-specifier 的成员。

  • 第1步:这是上述表单的声明,其中T = int,嵌套名称说明符 = bar::和{{1} }。我们首先查看声明D1 = foo::* ptrT D1

  • 第2步:我们再次应用相同的规则。 int foo::* ptr是上述表单的声明,其中int foo::* ptr = int,嵌套名称说明符 = Tfoo:: = {{1 }}。显然D1中标识符的类型是“ptr”,因此声明int ptr中标识符int的类型是“指向类{{1}成员的指针类型ptr“。

  • 第3步。我们回到原始声明; int foo::* ptrfoo)中标识符的类型是“指向类int的成员T D1的指针”,每步骤2,因此派生声明符-type-list 是“类型为”int foo::* ptr类成员的指针“。替换告诉我们,此声明声明foo是“指向类int的成员的指针,指向类foo的类ptr成员的类型指针。”

希望你永远不需要使用这样的怪物。

答案 3 :(得分:3)

如果有人想知道,你不能创建一个指向成员的指针,它可以深入嵌套多个图层。这样做的原因是所有指向成员的指针实际上比第一眼看上去更复杂;它们不仅仅包含该特定成员的特定偏移量。

由于虚拟继承等原因,使用简单的偏移不起作用;基本上可能发生这样的情况,即使在单一类型中,特定字段的偏移量在实例之间变化,因此指针到成员的分辨率需要在运行时完成。这主要是因为标准没有规定非POD类型的内部布局如何工作,因此无法使其静态工作。

如果是这种情况,使用普通的指向成员的指针无法进行两级深度解析,但需要编译器生成一个指针,使其包含一个深度指针的信息的两倍 - 到构件。

我想,由于指向成员的指针并不常见,因此当您仍然可以使用多个指针来实现相同的结果时,无需实际创建允许设置多层深层成员的语法。 / p>

答案 4 :(得分:1)

首先,为了帮助提高可读性&#34;你可以使用括号(编译将起作用):

struct bar;

struct foo
{
    int y;
    int (bar:: *whatever); // whatever is a pointer upon an int member of bar.
};

struct bar
{
    foo aFoo;
};

// ptr is a pointer upon a member of foo which points upon an int member of bar.
int (bar:: *(foo:: *ptr)) = &foo::whatever;

请注意

  

int(bar :: * whatever)

相当于

  

int(*无论如何)

有关于bar成员资格的约束。

至于

  

int(bar :: *(foo :: * ptr))

,相当于

  

int(*(* ptr))

有两个关于foo和bar成员资格的约束。

他们只是指针。他们不检查bar或foo是否真的有兼容的成员,因为这会阻止使用类bar的前向声明,类bar不会检查其他类是否通过指针引用其成员。此外,您可能还需要引用一个不透明的类(即,在单独的单元中定义类别栏)。

有用性如何?也许对于C ++反射来说,通过类包装器来设置/获取类成员的值?

template< typename Class, typename Type >
struct ClassMember
{
    using MemberPointer = Type (Class:: *);
    MemberPointer member;
    ClassMember(MemberPointer member) : member(member) {}
    void operator()(Class & object, Type value) { object.*member = value; }
    Type operator()(Class & object)  { return object.*member; }
};

template< typename Class, typename Type > ClassMember< Class, Type > MakeClassMember(Type(Class:: *member))
{
    return ClassMember< Class, Type >(member);
}

struct Rectangle
{
    double width;
    double height;

    Rectangle(double width = 0., double height = 0.) : width(width), height(height) {}
};

int main(int argc, char const *argv[])
{
    auto r = Rectangle(2., 1.);

    auto w = MakeClassMember(&Rectangle::width);
    auto h = MakeClassMember(&Rectangle::height);

    w(r, 3.);
    h(r, 2.);

    printf("Rectangle(%f, %f)\n", w(r), h(r));

    return 0;
}

当然,这个例子没有显示双成员指针的特定用法,因为我没有看到一个简单的方法来说明它或者从概念上说这是一个很好的理由。