(Perché) possiamo assegnare membri di classe non statici a variabili statiche nell'inizializzazione?

(Perché) possiamo assegnare membri di classe non statici a variabili statiche nell'inizializzazione?


In una base di codice più ampia ho riscontrato codice come questo (vedi su godbolt):


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

Ad essere sincero, sono sconcertato. Sembra che stia inizializzando un array statico di Foo puntatori in Bar con una variabile membro non statica di Base . Com'è possibile anche senza un oggetto?


(Disclaimer:si trova nel codice di produzione che funziona davvero, si spera che non si basi su UB?)


Inoltre, c'è qualche problema se rimuovo la ricerca del nome qualificato come qui? Vorrei rifattorizzare il codice e renderlo più leggibile. Tutti questi Bar:: Sembra abbastanza superfluo, ma poiché non mi sento così a mio agio con il codice, preferirei prima capire le implicazioni.


Risposte:


Per cominciare, Bar::mybar è una matrice di puntatori ai membri. Quelli non sono veri e propri suggerimenti. Sono più simili a un'astrazione su un offset nell'oggetto. Consentono l'accesso ai membri indirettamente. Dato un Base oggetto (o uno da esso derivato), possiamo invocarli, come segue


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

L'altra cosa da notare è che nel tuo esempio l'oggetto nell'ambito dello spazio dei nomi non è la definizione di Bar::mybar . È un array non correlato. La definizione corretta sarebbe


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

È perché la tua definizione era sbagliata che la rimozione di parte del nome qualificato non ha avuto effetto. Quando l'hai rimosso, sei rimasto con


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

I tipi di dichiarazione e "definizione" non corrispondono. Ma non hai errori perché in realtà sono oggetti diversi.


Nella definizione corretta, ogni Bar:: qualificatore è richiesto e ha uno scopo diverso. Uno serve per creare il tipo corretto (puntatore al membro di Bar ), l'altro designa il membro statico di Bar che è in fase di definizione ed è richiesto in ogni definizione di membro di classe statico.


Alcune risposte al codice


struct Foo {};
struct Base {
Foo* a;
};
struct Bar : public Base {
static Foo* Bar::*mybar[];
};
Foo* Bar::Bar::*mybar[] = {
&Base::a };
Foo *Bar::Bar::*mybar[] = {
&Base::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
aBar.*mybar[0] // This resolve to a Foo* inside aBar 
Foo* Bar::* Bar::mybar[] = {
&Base::a };
static Foo *mybar[];
Foo Bar::*mybar[] = {
&Base::a };