Perché non riesco a creare un vettore di riferimenti?


351

Quando faccio questo:

std::vector<int> hello;

Funziona tutto alla grande. Tuttavia, quando lo trasformo in un vettore di riferimenti invece:

std::vector<int &> hello;

Ottengo errori orribili come

errore C2528: 'pointer': il puntatore al riferimento è illegale

Voglio mettere un mucchio di riferimenti a strutture in un vettore, in modo da non dover intromettermi con i puntatori. Perché il vettore sta dando una scossa a questo proposito? È invece la mia unica opzione di utilizzare un vettore di puntatori?


46
puoi usare std :: vector <reference_wrapper <int>> ciao; See informit.com/guides/content.aspx?g=cplusplus&seqNum=217
Amit

2
@amit il link non è più valido, documentazione ufficiale qui
Martin

Risposte:


339

Il tipo di componente di contenitori come i vettori deve essere assegnabile . I riferimenti non sono assegnabili (puoi inizializzarli solo una volta quando vengono dichiarati e non puoi farli fare riferimento a qualcos'altro in seguito). Anche altri tipi non assegnabili non sono ammessi come componenti dei contenitori, ad esempio vector<const int>non è consentito.


1
Stai dicendo che non posso avere un vettore di vettori? (Sono sicuro di averlo fatto ...)
James Curran,

8
Sì, uno std :: vector <std :: vector <int>> è corretto, std :: vector è assegnabile.
Martin Cote,

17
In effetti, questa è la ragione "reale". L'errore sull'impossibilità di T * di T è U ed è solo un effetto collaterale del requisito violato che T deve essere assegnabile. Se il vettore fosse in grado di controllare con precisione il parametro type, probabilmente direbbe "requisito violato: T e non assegnabile"
Johannes Schaub - litb,

2
Verifica del concetto assegnabile su boost.org/doc/libs/1_39_0/doc/html/Assignable.html tutte le operazioni tranne lo swap sono valide sui riferimenti.
Amit

7
Questo non è più vero. A partire da C ++ 11, l'unico requisito indipendente dall'operazione per element è essere "cancellabile" e il riferimento no. Vedi stackoverflow.com/questions/33144419/… .
laike9m,

119

sì, puoi, cercare std::reference_wrapper, che imita un riferimento ma è assegnabile e può anche essere "ripristinato"


4
C'è un modo per aggirare la chiamata get()prima quando si tenta di accedere a un metodo di un'istanza di una classe in questo wrapper? Ad esempio, reference_wrapper<MyClass> my_ref(...); my_ref.get().doStuff();non è un riferimento molto simile.
timdiels,

3
Non è implacabilmente calcinabile sul Tipo stesso restituendo il riferimento?
WorldSEnder,

3
Sì, ma ciò richiede un contesto che implichi quale conversione è richiesta. L'accesso dei membri non lo fa, quindi la necessità di .get(). Ciò che Timdiels vuole è operator.; dai un'occhiata alle ultime proposte / discussioni al riguardo.
underscore_d

31

Per loro stessa natura, i riferimenti possono essere impostati solo nel momento in cui vengono creati; cioè, le seguenti due linee hanno effetti molto diversi:

int & A = B;   // makes A an alias for B
A = C;         // assigns value of C to B.

Inoltre, questo è illegale:

int & D;       // must be set to a int variable.

Tuttavia, quando si crea un vettore, non è possibile assegnare valori ai relativi elementi al momento della creazione. In sostanza, stai solo facendo un mucchio dell'intero esempio.


10
"quando crei un vettore, non c'è modo di assegnare valori ai suoi elementi al momento della creazione" Non capisco cosa intendi con questa affermazione. Quali sono i "suoi elementi alla creazione"? Posso creare un vettore vuoto. E posso aggiungere elementi con .push_back (). Stai solo sottolineando che i riferimenti non sono costruibili per impostazione predefinita. Ma posso sicuramente avere vettori di classi che non sono costruibili per difetto.
newacct,

2
Non è necessario che l'elemento ype di std :: vector <T> sia costruibile di default. Puoi scrivere struct A {A (int); privato: A (); }; vettore <A> a; bene - fintanto che non usi tali metodi che richiedono che sia predefinito costruibile (come v.resize (100); - ma invece dovrai fare v.resize (100, A (1));)
Johannes Schaub - litb

E come scriveresti un push_back () in questo caso? Userà ancora il compito, non la costruzione.
James Curran,

3
James Curran, Nessuna costruzione predefinita ha luogo lì. push_back solo posizionamento-notizie A nel buffer preallocato. Vedi qui: stackoverflow.com/questions/672352/… . Nota che la mia affermazione è solo che il vettore può gestire tipi non predefiniti costruibili. Non pretendo, ovviamente, che possa gestire T & (non può, ovviamente).
Johannes Schaub - litb

29

Ion Todirel ha già menzionato una risposta utilizzando std::reference_wrapper. Dal C ++ 11 abbiamo un meccanismo per recuperare l'oggetto da std::vector e rimuovere il riferimento usando std::remove_reference. Di seguito viene fornito un esempio compilato utilizzando g++e clangcon opzione
-std=c++11ed eseguito correttamente.

#include <iostream>
#include <vector>
#include<functional>

class MyClass {
public:
    void func() {
        std::cout << "I am func \n";
    }

    MyClass(int y) : x(y) {}

    int getval()
    {
        return x;
    }

private: 
        int x;
};

int main() {
    std::vector<std::reference_wrapper<MyClass>> vec;

    MyClass obj1(2);
    MyClass obj2(3);

    MyClass& obj_ref1 = std::ref(obj1);
    MyClass& obj_ref2 = obj2;

    vec.push_back(obj_ref1);
    vec.push_back(obj_ref2);

    for (auto obj3 : vec)
    {
        std::remove_reference<MyClass&>::type(obj3).func();      
        std::cout << std::remove_reference<MyClass&>::type(obj3).getval() << "\n";
    }             
}

12
Non vedo il valore std::remove_reference<>qui. Il punto di std::remove_reference<>è permetterti di scrivere "il tipo T, ma senza essere un riferimento se è uno". Quindi std::remove_reference<MyClass&>::typeè lo stesso della scrittura MyClass.
alastair,

2
Nessun valore in questo - puoi semplicemente scrivere for (MyClass obj3 : vec) std::cout << obj3.getval() << "\n"; (o for (const MyClass& obj3: vec)se dichiari getval()const, come dovresti).
Toby Speight,

14

boost::ptr_vector<int> funzionerà.

Modifica: è stato un suggerimento da utilizzare std::vector< boost::ref<int> >, che non funzionerà perché non è possibile creare un predefinito a boost::ref.


8
Ma puoi avere un vettore o tipi non predefiniti costruibili, giusto? Devi solo stare attento a non usare il ctor predefinito. del vettore
Manuel,

1
@Manuel: Or resize.
Corse di leggerezza in orbita

5
Fai attenzione, i contenitori puntatore di Boost assumono la proprietà esclusiva delle punte. Citazione : "Quando hai bisogno di semantica condivisa, questa libreria non è ciò di cui hai bisogno."
Matthäus Brandl,

12

È un difetto nel linguaggio C ++. Non è possibile prendere l'indirizzo di un riferimento, poiché tentare di farlo comporterebbe il riferimento all'indirizzo dell'oggetto a cui si fa riferimento e quindi non è mai possibile ottenere un puntatore a un riferimento. std::vectorfunziona con i puntatori ai suoi elementi, quindi i valori memorizzati devono poter essere puntati. Dovrai invece utilizzare i puntatori.


Immagino che potrebbe essere implementato usando un buffer void * e un posizionamento nuovo. Non che questo avrebbe molto senso.
peterchen,

55
"Difetto nella lingua" è troppo forte. È di progettazione. Non credo sia necessario che il vettore funzioni con i puntatori agli elementi. Tuttavia è necessario che l'elemento sia assegnabile. I riferimenti non lo sono.
Brian Neal,

Non puoi nemmeno prendere sizeofun riferimento.
David Schwartz,

1
non hai bisogno di un puntatore a un riferimento, hai il riferimento, se hai bisogno del puntatore, tieni semplicemente il puntatore stesso; non c'è problema da risolvere qui
Ion Todirel,

10

TL; DR

Usa std::reference_wrappercosì:

#include <functional>
#include <string>
#include <vector>
#include <iostream>

int main()
{
    std::string hello = "Hello, ";
    std::string world = "everyone!";
    typedef std::vector<std::reference_wrapper<std::string>> vec_t;
    vec_t vec = {hello, world};
    vec[1].get() = "world!";
    std::cout << hello << world << std::endl;
    return 0;
}

Demo

Risposta lunga

Come suggerisce la norma , per un contenitore standard Xcontenente oggetti di tipo T, Tdeve provenire Erasableda X.

Erasable significa che la seguente espressione è ben formata:

allocator_traits<A>::destroy(m, p)

Aè il tipo di allocatore del contenitore, mè un'istanza di allocatore ed pè un puntatore di tipo *T. Vedi qui per Erasabledefinizione.

Per impostazione predefinita, std::allocator<T>viene utilizzato come allocatore del vettore. Con l'allocatore predefinito, il requisito è equivalente alla validità di p->~T()(Notare che Tè un tipo di riferimento ed pè puntatore a un riferimento). Tuttavia, il puntatore a un riferimento è illegale , quindi l'espressione non è ben formata.


3

Come altri hanno già detto, probabilmente finirai per usare un vettore di puntatori.

Tuttavia, potresti prendere in considerazione l' idea di utilizzare un ptr_vector !


4
Questa risposta non è realizzabile, poiché un ptr_vector dovrebbe essere un archivio. Cioè eliminerà i puntatori alla rimozione. Quindi non è utilizzabile per il suo scopo.
Klemens Morgenstern,

0

Come suggeriscono gli altri commenti, sei limitato all'uso dei puntatori. Ma se aiuta, ecco una tecnica per evitare di affrontare direttamente i puntatori.

Puoi fare qualcosa di simile al seguente:

vector<int*> iarray;
int default_item = 0; // for handling out-of-range exception

int& get_item_as_ref(unsigned int idx) {
   // handling out-of-range exception
   if(idx >= iarray.size()) 
      return default_item;
   return reinterpret_cast<int&>(*iarray[idx]);
}

reinterpret_castnon è necessario
Xeverous,

1
Come mostrano le altre risposte, non siamo limitati a usare i puntatori.
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.