Perché le matrici di riferimenti sono illegali?


149

Il seguente codice non viene compilato.

int a = 1, b = 2, c = 3;
int& arr[] = {a,b,c,8};

Cosa dice lo standard C ++ a riguardo?

So che potrei dichiarare una classe che contiene un riferimento, quindi creare un array di quella classe, come mostrato di seguito. Ma voglio davvero sapere perché il codice sopra non si compila.

struct cintref
{
    cintref(const int & ref) : ref(ref) {}
    operator const int &() { return ref; }
private:
    const int & ref;
    void operator=(const cintref &);
};

int main() 
{
  int a=1,b=2,c=3;
  //typedef const int &  cintref;
  cintref arr[] = {a,b,c,8};
}

È possibile utilizzare struct cintrefinvece di const int &simulare una matrice di riferimenti.


1
Anche se l'array fosse valido, la memorizzazione di un valore "8" non funzionante. Se facessi "intlink value = 8;", morirebbe orribilmente, perché è praticamente tradotto in "const int & value = 8;". Un riferimento deve fare riferimento a una variabile.
Grant Peters,

3
intlink value = 8;funziona. controlla se non ci credi.
Alexey Malistov,

7
Come sottolinea Alexey, è perfettamente valido associare un valore a un riferimento const.
avakar,

1
Ciò che non funziona è quello operator=. I riferimenti non possono essere ripristinati. Se si vuole veramente tale semantica - anche se non ho personalmente trovato una situazione in cui sono praticamente utile - allora std::reference_wrappersarebbe il modo per farlo, in quanto memorizza in realtà un puntatore, ma fornisce di riferimento simile a operators e non permette riposizionamento. Ma poi userei solo un puntatore!
underscore_d

1
L'operatore = è privato e non implementato, ovvero ciò che in C ++ 11 è = cancella.
Jimmy Hartzell,

Risposte:


148

Rispondendo alla tua domanda sullo standard posso citare lo standard C ++ §8.3.2 / 4 :

Non devono esserci riferimenti a riferimenti, matrici di riferimenti e puntatori a riferimenti.


32
Cos'altro c'è da dire?
poliglotta,

9
ci dovrebbero essere matrici di riferimenti per lo stesso motivo per cui abbiamo matrici di puntatori, stesse informazioni ma gestione diversa, no?
neu-rah,

6
Se gli sviluppatori di compilatori decidessero, come estensione, di consentire matrici di riferimenti, sarebbe un successo? O ci sono alcuni problemi reali - forse un codice ambiguo - che renderebbe confusa la definizione di questa estensione?
Aaron McDaid

23
@AaronMcDaid Penso che la vera ragione - che è così evidentemente assente da questa e simili discussioni - è che se uno avesse una serie di riferimenti, come si potrebbe disambiguare tra l'indirizzo di un elemento e l'indirizzo del suo referente? L'obiezione immediata a "Non è possibile inserire un riferimento in una matrice / contenitore / qualunque cosa" sia quella possibile inserire un structmembro il cui unico membro è un riferimento. Ma poi, facendo così, ora hai sia un riferimento che un oggetto genitore da nominare ... il che significa che ora puoi indicare in modo inequivocabile quale indirizzo vuoi. Sembra ovvio ... quindi perché nessuno l'ha detto?
underscore_d

95
@polyglot What more is there to say?Una logica del perché ? Mi occupo dello standard, ma solo citarlo e supporre che sia la fine della discussione sembra un modo sicuro per distruggere il pensiero critico degli utenti - insieme a qualsiasi potenziale per il linguaggio di evolversi, ad esempio oltre i limiti di omissione o restrizione artificiale. C'è troppo "perché lo standard dice" qui - e non abbastanza "ed è molto ragionevole dirlo, per i seguenti motivi pratici". Non so perché i voti siano così entusiasti di risposte che dicono solo il primo senza nemmeno cercare di esplorare il secondo.
underscore_d

64

I riferimenti non sono oggetti. Non hanno una memoria propria, fanno semplicemente riferimento a oggetti esistenti. Per questo motivo non ha senso disporre di matrici di riferimenti.

Se si desidera un oggetto leggero che fa riferimento a un altro oggetto, è possibile utilizzare un puntatore. Sarai in grado di utilizzare a structcon un membro di riferimento come oggetti negli array se fornisci l'inizializzazione esplicita per tutti i membri di riferimento per tutte le structistanze. I riferimenti non possono essere disattivati ​​per impostazione predefinita.

Modifica: Come osserva jia3ep, nella sezione standard sulle dichiarazioni esiste un divieto esplicito sugli array di riferimenti.


6
I riferimenti hanno la stessa natura dei puntatori costanti e quindi occupano un po 'di memoria (memoria) per indicare qualcosa.
inazaruk,

7
Non necessariamente. Il compilatore potrebbe essere in grado di evitare di memorizzare l'indirizzo, ad esempio se si tratta di un riferimento all'oggetto locale.
EFraim,

4
Non necessariamente. I riferimenti nelle strutture di solito occupano un po 'di spazio. I riferimenti locali spesso non lo fanno. Ad ogni modo, in senso stretto, i riferimenti non sono oggetti e (questa era una novità per me) i riferimenti nominati non sono in realtà variabili .
CB Bailey,

26
Sì, ma questo è un dettaglio di implementazione. Un oggetto nel modello C ++ è una regione di archiviazione tipizzata. Un riferimento non è esplicitamente un oggetto e non vi è alcuna garanzia che occupi spazio in un determinato contesto.
CB Bailey,

9
Detto in questo modo: non puoi avere una struttura che non sia 16 refs di foo's e usarlo esattamente come vorrei usare il mio array di refs - tranne che non puoi indicizzare i 16 riferimenti foo . Questo è un linguaggio IMHO verruca.
Greggo,

28

Questa è una discussione interessante Chiaramente le matrici di ref sono del tutto illegali, ma IMHO il motivo per cui non è così semplice come dire "non sono oggetti" o "non hanno dimensioni". Vorrei sottolineare che gli array stessi non sono oggetti a tutti gli effetti in C / C ++ - se ti opponi a questo, prova a creare un'istanza di alcune classi di modelli stl usando un array come parametro di modello 'class' e vedi cosa succede. Non puoi restituirli, assegnarli, passarli come parametri. (un parametro di array viene trattato come un puntatore). Ma è legale creare array di array. I riferimenti hanno una dimensione che il compilatore può e deve calcolare - non puoi sizeof () un riferimento, ma puoi creare una struttura contenente nient'altro che riferimenti. Avrà una dimensione sufficiente per contenere tutti i puntatori che implementano i riferimenti. Puoi'

struct mys {
 int & a;
 int & b;
 int & c;
};
...
int ivar1, ivar2, arr[200];
mys my_refs = { ivar1, ivar2, arr[12] };

my_refs.a += 3  ;  // add 3 to ivar1

In effetti è possibile aggiungere questa linea alla definizione della struttura

struct mys {
 ...
 int & operator[]( int i ) { return i==0?a : i==1? b : c; }
};

... e ora ho qualcosa che sembra MOLTO come una serie di ref:

int ivar1, ivar2, arr[200];
mys my_refs = { ivar1, ivar2, arr[12] };

my_refs[1] = my_refs[2]  ;  // copy arr[12] to ivar2
&my_refs[0];               // gives &my_refs.a == &ivar1

Ora, questo non è un array reale, è un sovraccarico dell'operatore; non farà cose che le matrici normalmente fanno come sizeof (arr) / sizeof (arr [0]), per esempio. Ma fa esattamente quello che voglio fare una serie di riferimenti, con C ++ perfettamente legale. Tranne (a) è una seccatura da impostare per più di 3 o 4 elementi, e (b) sta facendo un calcolo usando un gruppo di?: Che potrebbe essere fatto usando l'indicizzazione (non con la normale indicizzazione C-pointer-calcolo-semantica , ma indicizzando comunque). Mi piacerebbe vedere un tipo 'array di riferimento' molto limitato che può effettivamente farlo. Vale a dire una matrice di riferimenti non verrebbe trattata come una matrice generale di cose che sono riferimenti, ma piuttosto sarebbe una nuova "matrice di riferimento" fare con i modelli).

questo probabilmente funzionerebbe, se non ti dispiace questo tipo di cattiva: rifusione '* this' come un array di int * 'e restituisce un riferimento fatto da uno: (non raccomandato, ma mostra come il giusto' array ' funzionerebbe):

 int & operator[]( int i ) { return *(reinterpret_cast<int**>(this)[i]); }

1
È possibile fare questo in C ++ 11 std::tuple<int&,int&,int&> abcref = std::tie( a,b,c)- che crea un "allineamento" di riferimenti che possono essere indicizzati solo da costanti in fase di compilazione utilizzando std :: get. Ma non puoi farlo std::array<int&,3> abcrefarr = std::tie( a,b,c). Forse c'è un modo per farlo che non conosco. Mi sembra che ci dovrebbe essere un modo per scrivere una specializzazione di std::array<T&,N>cui può fare questo - e quindi una funzione std::tiearray(...)che restituisce uno di questi (o consentire std::array<T&,N>di accettare l'inizializzatore contenente indirizzi = {& v1, & v2 ecc})
greggo

La tua proposta è sciocca (IMO) e la tua ultima riga presuppone che un int possa contenere un puntatore (di solito falso, almeno dove lavoro). Ma questa è l'unica risposta qui con una logica legittima per cui sono vietate le matrici di riferimenti, quindi +1
Nemo

@Nemo non era davvero inteso come una proposta, ma solo per illustrare che la funzionalità di una cosa del genere non è assurda sulla sua faccia. Nel mio ultimo commento noto che c ++ ha cose che si avvicinano. Inoltre, l'ultima riga a cui si fa riferimento presuppone che la memoria che implementa un riferimento a un int possa essere utilmente definita come un puntatore a int - la struttura contiene riferimenti, non ints - e ciò tende ad essere vero. Non sto affermando che sia portatile (o addirittura al sicuro da regole di "comportamento indefinito"). aveva lo scopo di illustrare come l'indicizzazione potesse essere usata sotto il cofano per evitare tutto?:
greggo

Giusto. Ma penso che questa idea sia "assurda sulla sua faccia", ed è proprio per questo che le matrici di riferimenti sono vietate. Un array è innanzitutto un contenitore. Tutti i contenitori richiedono un tipo di iteratore per l'applicazione di algoritmi standard. Il tipo di iteratore per "array di T" è normalmente "T *". Quale sarebbe il tipo di iteratore per una matrice di riferimenti? La quantità di hacking linguistico necessaria per affrontare tutti questi problemi è ridicola per una funzionalità essenzialmente inutile. In realtà forse farò di questo una mia risposta :-)
Nemo il

@Nemo Non è necessario che faccia parte del linguaggio principale, quindi il tipo di iteratore essendo "personalizzato" non è un grosso problema. Tutti i vari tipi di contenitore hanno i loro tipi di iteratore. In questo caso, l'iteratore, senza riferimenti, farebbe riferimento agli objs di destinazione, non ai riferimenti nell'array, il che è pienamente coerente con il comportamento dell'array. Potrebbe essere necessario un po 'di nuovo comportamento C ++ da supportare come modello Il semplicissimo std::array<T,N>, facilmente definito nel codice app, non è stato aggiunto a std :: fino a c ++ 11, presumibilmente perché è verrucoso avere questo senza alcun modo di si = {1,2,3};trattava di "hacking del linguaggio"?
Greggo,

28

Commenta la tua modifica:

La soluzione migliore è std::reference_wrapper .

Dettagli: http://www.cplusplus.com/reference/functional/reference_wrapper/

Esempio:

#include <iostream>
#include <functional>
using namespace std;

int main() {
    int a=1,b=2,c=3,d=4;
    using intlink = std::reference_wrapper<int>;
    intlink arr[] = {a,b,c,d};
    return 0;
}

Salve, questo è stato affermato nel 09. Suggerimento: cerca nuovi post :)
Stígandr,

9
Il post è vecchio ma non tutti conoscono la soluzione con reference_wrapper. Le persone hanno bisogno di leggere le mosse su STL e STDlib di C ++ 11.
Hai visto il

Ciò fornisce un collegamento e un codice di esempio, anziché menzionare semplicemente il nome della classe reference_wrapper.
David Stone,

12

Un array è implicitamente convertibile in un puntatore e il puntatore a riferimento è illegale in C ++


11
È vero che non puoi avere un puntatore a un riferimento, ma questo non è il motivo per cui non puoi avere un array di riferimenti. Piuttosto sono entrambi sintomi del fatto che i riferimenti non sono oggetti.
Richard Corden,

1
Una struttura non può contenere altro che riferimenti e avrà una dimensione proporzionale al numero di essi. Se avessi una serie di ref 'arr', la normale conversione da array a puntatore non avrebbe senso, dal momento che 'puntatore a riferimento' non ha senso. ma avrebbe senso usare solo arr [i], allo stesso modo di st.ref quando 'st' è una struttura contenente un riferimento. Hmm. Ma & arr [0] darebbe l'indirizzo del primo oggetto referenziato, e & arr [1] - & arr [0] non sarebbe lo stesso di & arr [2] - & arr [1] - creerebbe molta stranezza.
Greggo,

11

Dato int& arr[] = {a,b,c,8};, che cos'è sizeof(*arr)?

Ovunque altro, un riferimento viene trattato come semplicemente la cosa stessa, quindi sizeof(*arr)dovrebbe essere semplicemente sizeof(int). Ma ciò renderebbe errata l'aritmetica del puntatore di array su questo array (supponendo che i riferimenti non abbiano le stesse larghezze in pollici). Per eliminare l'ambiguità, è vietato.


Sì. Non puoi avere una matrice di qualcosa a meno che non abbia una dimensione. Qual è la dimensione di un riferimento?
David Schwartz,

4
@DavidSchwartz Crea un structmembro il cui unico membro è quel riferimento e scoprilo ..?
underscore_d

3
Questa dovrebbe essere la risposta accettata, non quella stupida pedante che lancia semplicemente lo standard all'OP.
Tyson Jacobs,

Forse c'è un refuso. "supponendo che i riferimenti non abbiano le stesse larghezze degli ints" dovrebbe essere "supponendo che i riferimenti non abbiano le stesse larghezze degli ints". Cambiare è a come .
Zhenguoli,

Ma aspetta, con questa logica non puoi avere variabili membro di riferimento.
Tyson Jacobs,

10

Perché come molti hanno già detto qui, i riferimenti non sono oggetti. sono semplicemente alias. È vero che alcuni compilatori potrebbero implementarli come puntatori, ma lo standard non impone / specifica ciò. E poiché i riferimenti non sono oggetti, non puoi indicarli. Memorizzare gli elementi in un array significa che esiste un qualche tipo di indirizzo di indice (cioè, che punta a elementi in un certo indice); ed è per questo che non puoi avere matrici di riferimenti, perché non puoi indicarle.

Usa boost :: reference_wrapper o boost :: tuple invece; o solo puntatori.


5

Credo che la risposta sia molto semplice e abbia a che fare con le regole di riferimento semantiche e il modo in cui gli array vengono gestiti in C ++.

In breve: i riferimenti possono essere pensati come strutture che non hanno un costruttore predefinito, quindi si applicano tutte le stesse regole.

1) Semanticamente, i riferimenti non hanno un valore predefinito. I riferimenti possono essere creati solo facendo riferimento a qualcosa. I riferimenti non hanno un valore per rappresentare l'assenza di un riferimento.

2) Quando si alloca un array di dimensioni X, il programma crea una raccolta di oggetti inizializzati di default. Poiché il riferimento non ha un valore predefinito, la creazione di un tale array è semanticamente illegale.

Questa regola si applica anche alle strutture / classi che non hanno un costruttore predefinito. Il seguente esempio di codice non viene compilato:

struct Object
{
    Object(int value) { }
};

Object objects[1]; // Error: no appropriate default constructor available

1
È possibile creare un array di Object, devi solo fare in modo di inizializzare tutti: Object objects[1] = {Object(42)};. Nel frattempo, non è possibile creare una matrice di riferimenti, anche se si inizializzano tutti.
Anton3

4

Puoi avvicinarti abbastanza con questa struttura del modello. Tuttavia, è necessario inizializzare con espressioni che sono puntatori a T, anziché a T; e così, anche se puoi facilmente creare un 'fake_constref_array' allo stesso modo, non sarai in grado di legarlo a valori come nell'esempio del PO ('8');

#include <stdio.h>

template<class T, int N> 
struct fake_ref_array {
   T * ptrs[N];
  T & operator [] ( int i ){ return *ptrs[i]; }
};

int A,B,X[3];

void func( int j, int k)
{
  fake_ref_array<int,3> refarr = { &A, &B, &X[1] };
  refarr[j] = k;  // :-) 
   // You could probably make the following work using an overload of + that returns
   // a proxy that overloads *. Still not a real array though, so it would just be
   // stunt programming at that point.
   // *(refarr + j) = k  
}

int
main()
{
    func(1,7);  //B = 7
    func(2,8);     // X[1] = 8
    printf("A=%d B=%d X = {%d,%d,%d}\n", A,B,X[0],X[1],X[2]);
        return 0;
}

-> A = 0 B = 7 X = {0,8,0}


2

Un oggetto di riferimento non ha dimensioni. Se scrivi sizeof(referenceVariable), ti darà la dimensione dell'oggetto a cui fa riferimento referenceVariable, non quella del riferimento stesso. Non ha dimensioni proprie, motivo per cui il compilatore non è in grado di calcolare la dimensione richiesta dall'array.


3
in tal caso, come può un compilatore calcolare la dimensione di una struttura contenente solo un riferimento?
Juster,

Il compilatore conosce la dimensione fisica di un riferimento - è lo stesso di un puntatore. I riferimenti sono, infatti, puntatori semanticamente glorificati. La differenza tra puntatori e riferimenti è che i riferimenti hanno parecchie regole semantiche poste su di essi al fine di ridurre il numero di bug che potresti creare rispetto ai puntatori.
Kristupas A.

2

Quando si memorizza qualcosa in un array, è necessario conoscerne le dimensioni (poiché l'indicizzazione dell'array si basa sulla dimensione). Secondo lo standard C ++ Non è specificato se un riferimento debba essere archiviato o meno, pertanto non sarebbe possibile indicizzare una matrice di riferimenti.


1
Ma inserendo detto riferimento in a struct, magicamente tutto diventa specificato, ben definito, garantito e di dimensioni deterministiche. Perché, quindi, esiste questa differenza apparentemente totalmente artificiale?
underscore_d

... ovviamente, se uno inizia davvero a pensare a ciò che structconferisce il wrapping , alcune buone risposte possibili iniziano a suggerire se stesse - ma per me nessuno lo ha ancora fatto, nonostante questa sia una delle sfide immediate per tutti i comuni motivi per cui non è possibile utilizzare riferimenti non scartati.
underscore_d

2

Solo per aggiungere a tutta la conversazione. Poiché le matrici richiedono posizioni di memoria consecutive per memorizzare l'elemento, quindi se creiamo una matrice di riferimenti, non è garantito che si trovino in una posizione di memoria consecutiva, quindi l'accesso sarà un problema e quindi non possiamo nemmeno applicare tutte le operazioni matematiche su Vettore.


Considera il codice seguente dalla domanda originale: int a = 1, b = 2, c = 3; int & arr [] = {a, b, c, 8}; C'è molto meno possibilità che a, b, c risiedano nella posizione di memoria consecutiva.
Amit Kumar,

Questo è lontano dal problema principale con il concetto di contenere riferimenti in un contenitore.
underscore_d

0

Prendi in considerazione una serie di puntatori. Un puntatore è davvero un indirizzo; quindi quando inizializzi l'array, stai analogamente dicendo al computer "alloca questo blocco di memoria per contenere questi numeri X (che sono indirizzi di altri elementi)". Quindi se cambi uno dei puntatori, stai semplicemente cambiando ciò a cui punta; è ancora un indirizzo numerico che si trova nello stesso punto.

Un riferimento è analogo a un alias. Se dovessi dichiarare una serie di riferimenti, in pratica diresti al computer "allocare questa massa amorfa di memoria costituita da tutti questi diversi elementi sparsi".


1
Perché, quindi, fare un structmembro il cui unico membro è un riferimento risulta in qualcosa di completamente legale, prevedibile e non amorfo? Perché un utente deve racchiudere un riferimento affinché possa ottenere magicamente poteri capaci di array o è solo una restrizione artificiale? IMO nessuna risposta ha affrontato direttamente questo. Suppongo che la confutazione sia "L'oggetto di avvolgimento ti assicura di poter usare la semantica del valore, nella forma dell'oggetto di avvolgimento, in modo che tu possa avere le garanzie di valori, che sono necessari perché, ad esempio, come si sarebbe inequivocabili tra elemento e indirizzo dell'arbitro" ... È quello che volevi dire?
underscore_d

-2

In realtà, questa è una miscela di sintassi C e C ++.

Si dovrebbe o utilizzare le matrici C puri, che non possono essere di riferimenti, in quanto riferimento sono parte di C ++ solo. Oppure vai in C ++ e usa il tasto std::vectorostd::array classe per il tuo scopo.

Per quanto riguarda la parte modificata: anche se structè un elemento di C, si definiscono funzioni di costruzione e di operatore, che lo rendono un C ++ class. Di conseguenza, structnon verrai compilato in C puro!


Qual è il tuo punto? std::vectoro std::arraynon può contenere riferimenti neanche. Lo standard C ++ è abbastanza esplicito che nessun contenitore può.
underscore_d
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.