Warum kann ich keinen Referenzvektor erstellen?

351

Wenn ich das mache:

std::vector<int> hello;

Alles funktioniert super. Wenn ich es jedoch stattdessen zu einem Referenzvektor mache:

std::vector<int &> hello;

Ich bekomme schreckliche Fehler wie

Fehler C2528: 'Zeiger': Zeiger auf Referenz ist unzulässig

Ich möchte eine Reihe von Verweisen auf Strukturen in einen Vektor einfügen, damit ich mich nicht in Zeiger einmischen muss. Warum löst Vektor einen Wutanfall aus? Ist meine einzige Option, stattdessen einen Zeigervektor zu verwenden?

Colen
quelle
46
Sie können std :: vector <reference_wrapper <int>> verwenden. hallo; Siehe informit.com/guides/content.aspx?g=cplusplus&seqNum=217
amit
2
@amit der Link ist nicht mehr gültig, offizielle Dokumentation hier
Martin

Antworten:

339

Der Komponententyp von Containern wie Vektoren muss zuweisbar sein . Referenzen können nicht zugewiesen werden (Sie können sie nur einmal initialisieren, wenn sie deklariert sind, und Sie können sie später nicht auf etwas anderes verweisen lassen). Andere nicht zuweisbare Typen sind ebenfalls nicht als Bestandteile von Containern vector<const int>zulässig , z . B. nicht zulässig.

newacct
quelle
1
Wollen Sie damit sagen, dass ich keinen Vektor von Vektoren haben kann? (Ich bin sicher, dass ich das getan habe ...)
James Curran
8
Ja, ein std :: vector <std :: vector <int>> ist korrekt, std :: vector ist zuweisbar.
Martin Cote
17
Dies ist in der Tat der "eigentliche" Grund. Der Fehler, dass T * für T unmöglich ist, ist U & ist nur ein Nebeneffekt der verletzten Anforderung, dass T zuweisbar sein muss. Wenn der Vektor in der Lage wäre, den Typparameter genau zu überprüfen, würde er wahrscheinlich "verletzte Anforderung: T & nicht zuweisbar" sagen
Johannes Schaub - litb
2
Überprüfen des zuweisbaren Konzepts unter boost.org/doc/libs/1_39_0/doc/html/Assignable.html Alle Vorgänge mit Ausnahme des Austauschs sind für Referenzen gültig.
Amit
7
Das stimmt nicht mehr. Seit C ++ 11 ist die einzige betriebsunabhängige Anforderung für das Element "Löschbar" und die Referenz nicht. Siehe stackoverflow.com/questions/33144419/… .
Laike9m
119

Ja, Sie können suchen std::reference_wrapper, dass dies eine Referenz imitiert, aber zuweisbar ist und auch "neu eingesetzt" werden kann.

Ion Todirel
quelle
4
Gibt es eine Möglichkeit, den get()ersten Aufruf zu umgehen, wenn Sie versuchen, auf eine Methode einer Instanz einer Klasse in diesem Wrapper zuzugreifen? ZB reference_wrapper<MyClass> my_ref(...); my_ref.get().doStuff();ist nicht sehr referenzartig.
Timdiels
3
Ist es nicht implizit auf den Typ selbst umsetzbar, indem die Referenz zurückgegeben wird?
WorldSEnder
3
Ja, aber das erfordert einen Kontext, der impliziert, welche Konvertierung erforderlich ist. Der Zugang der Mitglieder tut dies nicht, daher ist dies erforderlich .get(). Was Timdiels will, ist operator.; Schauen Sie sich die neuesten Vorschläge / Diskussionen dazu an.
underscore_d
31

Referenzen können naturgemäß nur zum Zeitpunkt ihrer Erstellung festgelegt werden. dh die folgenden zwei Zeilen haben sehr unterschiedliche Auswirkungen:

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

Weiter ist das illegal:

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

Wenn Sie jedoch einen Vektor erstellen, können Sie seinen Elementen bei der Erstellung keine Werte zuweisen. Sie machen im Wesentlichen nur eine ganze Reihe des letzten Beispiels.

James Curran
quelle
10
"Wenn Sie einen Vektor erstellen, gibt es keine Möglichkeit, seinen Elementen bei der Erstellung Werte zuzuweisen." Ich verstehe nicht, was Sie mit dieser Aussage meinen. Was sind "seine Gegenstände bei der Schöpfung"? Ich kann einen leeren Vektor erstellen. Und ich kann Elemente mit .push_back () hinzufügen. Sie weisen nur darauf hin, dass Referenzen nicht standardmäßig konstruierbar sind. Aber ich kann definitiv Vektoren von Klassen haben, die nicht standardmäßig konstruierbar sind.
Newacct
2
Das Element ype von std :: vector <T> muss nicht standardmäßig konstruierbar sein. Sie können struct A {A (int) schreiben; privat: A (); }; Vektor <A> a; ganz gut - solange Sie keine Methoden verwenden, für die es standardmäßig konstruierbar sein muss (wie v.resize (100); - stattdessen müssen Sie v.resize (100, A (1)) ausführen;)
Johannes Schaub - litb
Und wie würden Sie in diesem Fall ein solches push_back () schreiben? Es wird weiterhin eine Zuordnung verwendet, keine Konstruktion.
James Curran
3
James Curran, dort findet keine Standardkonstruktion statt. push_back nur Placement-News A in den vorab zugewiesenen Puffer. Siehe hier: stackoverflow.com/questions/672352/… . Beachten Sie, dass meine Behauptung nur ist, dass der Vektor nicht standardmäßig konstruierbare Typen verarbeiten kann. Ich behaupte natürlich nicht, dass es mit T & umgehen könnte (das kann es natürlich nicht).
Johannes Schaub - litb
29

Ion Todirel erwähnte bereits eine Antwort JA mit std::reference_wrapper. Seit C ++ 11 haben wir einen Mechanismus zum Abrufen von Objekten aus std::vector und Entfernen der Referenz mithilfe von std::remove_reference. Im Folgenden finden Sie ein Beispiel, das mit g++und clangmit Option kompiliert
-std=c++11und erfolgreich ausgeführt wurde.

#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";
    }             
}
Steephen
quelle
12
Ich sehe den Wert std::remove_reference<>hier nicht. Der Punkt von std::remove_reference<>ist, Ihnen zu erlauben, "den Typ T zu schreiben, aber ohne eine Referenz zu sein, wenn es einer ist". So std::remove_reference<MyClass&>::typeist es auch mit dem Schreiben MyClass.
Alastair
2
Darin gibt es überhaupt keinen Wert - Sie können einfach schreiben for (MyClass obj3 : vec) std::cout << obj3.getval() << "\n"; (oder for (const MyClass& obj3: vec)wenn Sie getval()const deklarieren , wie Sie sollten).
Toby Speight
14

boost::ptr_vector<int> wird funktionieren.

Bearbeiten: war ein zu verwendender Vorschlag std::vector< boost::ref<int> >, der nicht funktioniert, da Sie a nicht standardmäßig erstellen können boost::ref.

Drew Dormann
quelle
8
Aber Sie können einen Vektor oder nicht standardmäßig konstruierbare Typen haben, oder? Sie müssen nur darauf achten, den Standard-Ctor nicht zu verwenden. des Vektors
Manuel
1
@ Manuel: Or resize. En
Leichtigkeitsrennen im Orbit
5
Seien Sie vorsichtig, die Zeigercontainer von Boost übernehmen das ausschließliche Eigentum an den Pointees. Zitat : "Wenn Sie eine gemeinsame Semantik benötigen, ist diese Bibliothek nicht das, was Sie brauchen."
Matthäus Brandl
12

Es ist ein Fehler in der C ++ - Sprache. Sie können die Adresse einer Referenz nicht übernehmen, da dies dazu führen würde, dass auf die Adresse des Objekts verwiesen wird, und Sie daher niemals einen Zeiger auf eine Referenz erhalten können. std::vectorarbeitet mit Zeigern auf seine Elemente, daher muss auf die gespeicherten Werte verwiesen werden können. Sie müssen stattdessen Zeiger verwenden.

Adam Rosenfield
quelle
Ich denke, es könnte mit einem void * -Puffer implementiert und neu platziert werden. Nicht, dass dies viel Sinn machen würde.
Peterchen
55
"Fehler in der Sprache" ist zu stark. Es ist beabsichtigt. Ich denke nicht, dass es erforderlich ist, dass der Vektor mit Zeigern auf Elemente arbeitet. Es ist jedoch erforderlich, dass das Element zuweisbar ist. Referenzen gibt es nicht.
Brian Neal
Sie können auch keine sizeofReferenz nehmen.
David Schwartz
1
Sie brauchen keinen Zeiger auf eine Referenz, Sie haben die Referenz. Wenn Sie den Zeiger brauchen, behalten Sie einfach den Zeiger selbst. Hier ist kein Problem zu lösen
Ion Todirel
10

TL; DR

Verwenden Sie std::reference_wrapperwie folgt:

#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

Lange Antwort

Wie Standard schlägt vor , für einen Standard - Container, XObjekte des Typs enthalten T, Tmüssen Erasableaus X.

Erasable bedeutet, dass der folgende Ausdruck gut geformt ist:

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

A ist der Allokatortyp des Containers. m ist eine Allokatorinstanz und pein Zeiger vom Typ *T. Siehe hier für die ErasableDefinition.

Standardmäßig std::allocator<T>wird als Vektor-Allokator verwendet. Mit dem Standardzuweiser entspricht die Anforderung der Gültigkeit von p->~T()(Beachten Sie, dass dies Tein Referenztyp und pein Zeiger auf eine Referenz ist). Jedoch, Zeiger auf eine Referenz ist jedoch unzulässig , daher ist der Ausdruck nicht gut geformt.

ivaigult
quelle
3

Wie andere bereits erwähnt haben, werden Sie wahrscheinlich stattdessen einen Zeigervektor verwenden.

Möglicherweise möchten Sie jedoch stattdessen einen ptr_vector verwenden!

Martin Cote
quelle
4
Diese Antwort ist nicht praktikabel, da ein ptr_vector ein Speicher sein soll. Das heißt, es werden die Zeiger beim Entfernen gelöscht. Es ist also nicht für seinen Zweck verwendbar.
Klemens Morgenstern
0

Wie aus den anderen Kommentaren hervorgeht, können Sie nur Zeiger verwenden. Aber wenn es hilft, ist hier eine Technik, um zu vermeiden, direkt mit Zeigern zu konfrontieren.

Sie können Folgendes tun:

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]);
}
Omid
quelle
reinterpret_castwird nicht benötigt
Xeverous
1
Wie die anderen Antworten zeigen, sind wir nicht darauf beschränkt, Zeiger zu verwenden.
underscore_d