(为什么)我们可以在初始化中将非静态类成员分配给静态变量吗?

(Why) can we assign non-static class members to static variables in initialization?

本文关键字:分配 成员 静态类 静态 变量 我们 为什么 初始化      更新时间:2023-10-16

在更大的代码库中,我遇到了这样的代码(参见godbolt(:

struct Foo {};
struct Base {
Foo* a;
};
struct Bar : public Base {
static Foo* Bar::*mybar[];
};
Foo* Bar::Bar::*mybar[] = {
&Base::a
};

老实说,我很困惑。这看起来像是用非静态成员变量Base初始化Bar中的Foo指针的静态数组。没有对象怎么可能?

(免责声明:这在实际有效的生产代码中找到 - 希望不依赖 UB?

另外,如果我像这里一样删除限定名称查找是否有任何问题?我想重构代码并使其更具可读性。所有这些Bar::似乎都是多余的,但由于我对代码不太满意,我宁愿先了解其中的含义。

与普通指针不同,类成员指针可以作为类的偏移量,它们告诉您它们指向对象的哪个成员。 因此,在您的代码中,mybar是一个类成员指针数组。 当你这样做时

Foo *Bar::Bar::*mybar[] = {
&Base::a
};

使用指向Basea成员的指针初始化数组。 这实际上并不指向a,它只是告诉编译器,如果您使用对象访问它,要返回对象的哪个成员。 那看起来像

Base foo; // now we have an actual object
foo.*mybar[0]; // access the `a` member of `foo` by using the "offset"

没有对象怎么可能?

可以在没有对象的情况下创建指向成员变量的指针。指针可用于仅在存在对象的情况下取消引用实际成员。

更简单的例子:

struct Foo { int m; int n};
using MemberPtr = int Foo::*;
MemberPtr p1 = &Foo::m;  // Instance of Foo is not needed.
MemberPtr p2 = &Foo::n;  // Instance of Foo is not needed.
*p1 = 10; //  Not allowed.
*p2 = 20; //  Not allowed.
Foo a;
a.*p1 = 10;  // Changes a.m
a.*p2 = 20;  // Changes a.n
Foo b;
b.*p1 = 30;  // Changes b.m
b.*p2 = 40;  // Changes b.n

请注意,您可以使用指向成员变量的同一指针更改类的两个实例的成员值。

首先,Bar::mybar是指向成员的指针数组。这些不是实际的指针。它们更像是对对象偏移量的抽象。它们允许间接访问成员。给定一个Base对象(或从它派生的对象(,我们可以调用它们,如下所示

aBar.*mybar[0] // This resolve to a Foo* inside aBar

另一件需要注意的事情是,在您的示例中,命名空间范围内的对象不是Bar::mybar的定义。这是一个不相关的数组。正确的定义是

Foo* Bar::* Bar::mybar[] = {
&Base::a
};

这是因为您的定义是错误的,因此删除某些限定名称不起作用。当您删除它时,您只剩下

static Foo *mybar[];
Foo Bar::*mybar[] = {
&Base::a
};

声明的类型与"定义"不匹配。但是你没有错误,因为这些实际上是不同的对象。

在正确的定义中,每个Bar::限定符都是必需的,并且具有不同的目的。一个用于创建正确的类型(指向Bar成员的指针(,另一个用于指定正在定义的Bar的静态成员,并且在每个静态类成员定义中都是必需的。