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


69

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;

实际上可以编译

但是,我不知道如何分配有用的东西。没有下列作品:

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告诉我指向的成员的指针barbar对象不兼容?我将如何使用int bar::*foo::*,以及如何构造有效的?


这看起来像是一种round回的方式,尝试decltype()使用适当的模板提供什么?
slashmais 2014年

6
也许我对这个主题不是很了解,但是如果它可以帮助int bar::*foo::*ptr = *new decltype(ptr);编译,但是我不知道它做什么,并且我似乎无法对结果做任何事情。这也是内存泄漏,但是有时必须以科学的名义牺牲内存。
IllusiveBrian

哎呀,当编译器作者实现这一点时,我发誓他们对谁会使用此东西发了很多誓。
marczellm 2014年

您的第一个代码示例语法错误。它必须是:int(foo :: * ptr)()=&foo :: x;
null0年

它可能就像Java中的反射……对于某些深奥的东西非常有用,例如实现诸如BeanUtils之类的库,但几乎永远不是一个好主意。
trognanders 2014年

Answers:


54

这是初始化这种怪兽的“有效”方法:

struct bar;

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

struct bar
{
    foo aFoo;
};

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

如我们所见,ptrfoo()的成员的指针foo::*,从右向左读取,其中该成员本身是barbar::*)的成员的指针,其中该成员是int。

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

希望您不会!但是,如果您受到胁迫,请尝试此!

struct bar
{
    foo aFoo;

    int really;
};

int bar::* foo::* ptr = &foo::whatever;
foo fleh;
fleh.whatever = &bar::really;
bar blah;
blah.*(fleh.*ptr) = 42;
std::cout << blah.really << std::endl;

40
我不确定我是否会解析它,但这不是我第一次不了解C ++是如何解析的。
zneak

这并不总是怪物。考虑迭代器foo的情况,由于某种原因,它需要引用其容器和保存有头部迭代器的容器栏。
2014年

19

那将是一个指向数据成员的指针,该指针本身就是一个指向数据成员(的int成员bar)的指针。

不要问我它实际上有什么用-我的脑袋有点旋转:)

编辑:这是它的完整示例:

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

输出当然是42。

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

#include <iostream>

struct bar {
    int f_bar(int i) { return i; };
};

struct foo {
    int(bar::*f_foo())(int)
    {
        return &bar::f_bar;
    }
};

int main()
{
    int(bar::*((foo::*ptr)()))(int) = &foo::f_foo;

    bar b;
    foo f;

    std::cout << (b.*((f.*ptr)()))(42);
}

就在我以为我开始理解其中的一些陈述时,您的第二个例子却粉碎了我的希望。
Cengiz Can

我理解第二个示例为何粉碎了您的希望:)。
02/13下滑

1)bar定义了一个名为f_bar的方法,该方法具有整数参数并返回整数。到目前为止很容易理解。2)foo定义了一个不带参数的方法f_foo,并返回一个指向具有整数参数并返回一个整数的bar方法的指针。我承认不太容易阅读。3)main首先将本地'ptr'定义为指向不带参数的foo方法的指针,并返回指向具有整数参数的bar方法的指针,并返回一个整数,并使用foo的f_foo方法将其赋值。天哪,描述它太长了!未完待续 !
2016年

4)由于f_bar和f_foo是方法,因此您需要定义b和f,以便可以调用(f.*ptr)()return &bar::f_bar。因此std::cout << (b.*((f.*ptr)()))(42);基本上与相同std::cout << (b.*(&bar::f_bar))(42);。5)哦,等等!呼叫'(b。*(&bar :: f_bar))(42);' 与调用'b.f_bar(42);`相同!所以你的确输出42
2016年

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

  • 步骤1:这是上述形式的声明,其中T= int,nested-name-specifier =bar::D1 = foo::* ptr。我们首先来看一下声明T D1,或者int foo::* ptr

  • 步骤2:我们再次应用相同的规则。int foo::* ptr是上述形式的声明,其中T= int,nested-name-specifier =foo::D1= ptr。显然,其中的标识符类型int ptr是“ int”,因此ptr声明中的标识符类型int foo::* ptr是“指向foo类型的类成员的指针int”。

  • 步骤3.返回原始声明;在标识符的类型T D1int foo::* ptr)是“指针类的成员foo类型的int”每步2,所以导出的说明符类型列表是“指针类的成员foo类型的”。换人告诉我们,这个声明声明ptr是“指针类的成员foo类型的指针到类的成员bar类型的int”。

希望您永远不需要使用这种怪物。


如果我们int bar::*(foo::*ptr) = nullptr;改为声明,会有区别吗?

@DmitryFucintv不,绑定是相同的。
TC

3

万一有人想知道,您不能创建一个指向成员的指针,该指针嵌套多层深层。原因是所有指向成员的指针实际上都比乍看之下要复杂得多。它们不只是包含该特定成员的特定偏移量。

由于虚拟继承等原因,无法使用简单的偏移量;基本上,即使在单一类型中,特定字段的偏移也会在实例之间发生变化,因此,需要在运行时完成指针到成员的解析。大多数情况下,这是由于该标准未指定非POD类型的内部布局可能如何工作的事实,因此无法使其静态工作。

如果是这种情况,则无法使用普通的指针到成员来完成两级深度解析,而是需要编译器生成一个指针,以使其包含一个深度指针到成员的信息的两倍。 。

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


1

首先,为了提高“可读性”,您可以使用括号(编译将起作用):

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 :: *任何)

相当于

int(*无论如何)

关于酒吧会员资格的限制。

至于

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

,相当于

整数(*(* 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;
}

当然,此示例未显示双成员指针的特定用法,因为在此我看不到一种简单的方法来说明它,也不是从概念上讲这样做的充分理由。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.