Dlaczego nie mogę utworzyć wektora odniesień?

351

Kiedy to zrobię:

std::vector<int> hello;

Wszystko działa świetnie. Kiedy jednak zrobię z tego wektor odnośników:

std::vector<int &> hello;

Dostaję straszne błędy jak

błąd C2528: „wskaźnik”: wskaźnik do odwołania jest nielegalny

Chcę umieścić wiele wektorów struktur w wektorze, aby nie musiałem mieszać się ze wskaźnikami. Dlaczego wektor wywołuje w tym napad złości? Czy moją jedyną opcją jest użycie wektora wskaźników?

Colen
źródło
46
możesz użyć std :: vector <reference_wrapper <int>> hello; Zobacz informit.com/guides/content.aspx?g=cplusplus&seqNum=217
Amit
2
@amit link nie jest już ważny, oficjalna dokumentacja tutaj
Martin

Odpowiedzi:

339

Typ komponentów pojemników, takich jak wektory, musi być możliwy do przypisania . Odniesień nie można przypisać (możesz je zainicjować tylko raz, gdy zostaną zadeklarowane, i nie możesz później zmuszać ich do odwoływania się do czegoś innego). Inne typy nieprzenoszalne również nie są dozwolone jako elementy kontenerów, np. vector<const int>Nie są dozwolone.

newacct
źródło
1
Mówisz, że nie mogę mieć wektora wektorów? (Jestem pewien, że to zrobiłem ...)
James Curran
8
Tak, std :: vector <std :: vector <int>> jest poprawny, std :: vector można przypisać.
Martin Cote
17
Rzeczywiście jest to „rzeczywisty” powód. Błąd polegający na tym, że T * jest niemożliwy dla T, jest U i jest tylko efektem ubocznym naruszonego wymogu, że T musi być możliwe do przypisania. Jeśli wektor byłby w stanie dokładnie sprawdzić parametr typu, to prawdopodobnie powiedziałby „naruszone wymaganie: nie można przypisać”
Johannes Schaub - litb
2
Sprawdzanie koncepcji możliwej do przypisania na stronie boost.org/doc/libs/1_39_0/doc/html/Assignable.html wszystkie operacje oprócz wymiany są poprawne dla referencji.
amit
7
To już nie jest prawda. Od C ++ 11 jedynym wymaganiem niezależnym od operacji dla elementu jest „Kasowalny”, a odwołanie nie. Zobacz stackoverflow.com/questions/33144419/... .
laike9m
119

tak, możesz poszukać std::reference_wrapper, który naśladuje odniesienie, ale można go przypisać i można go również „zresetować”

Jon Todirel
źródło
4
Czy jest jakiś sposób obejścia dzwonienia, get()gdy próbujesz uzyskać dostęp do metody instancji klasy w tym opakowaniu? Np. reference_wrapper<MyClass> my_ref(...); my_ref.get().doStuff();Nie jest bardzo odniesienia.
timdiels
3
Czy nie można go bezzwłocznie rzucić na sam typ, zwracając odwołanie?
WorldSEnder
3
Tak, ale wymaga to kontekstu wskazującego, która konwersja jest wymagana. Dostęp członków tego nie robi, stąd potrzeba .get(). Czego chce Timdielsoperator. ; spójrz na najnowsze propozycje / dyskusje na ten temat.
underscore_d
31

Ze swej natury odniesienia można ustawiać tylko w momencie ich tworzenia; tj. następujące dwie linie mają bardzo różne efekty:

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

Ponadto jest to nielegalne:

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

Jednak podczas tworzenia wektora nie ma możliwości przypisania wartości do jego elementów podczas tworzenia. Zasadniczo robisz całą masę ostatniego przykładu.

James Curran
źródło
10
„kiedy tworzysz wektor, nie ma możliwości przypisania wartości do jego elementów podczas tworzenia” Nie rozumiem, co masz na myśli przez to stwierdzenie. Co to są „jego elementy przy tworzeniu”? Mogę stworzyć pusty wektor. I mogę dodawać elementy za pomocą .push_back (). Wskazujesz tylko, że odniesienia nie są konstruowane domyślnie. Ale zdecydowanie mogę mieć wektory klas, które nie są domyślnie konstruowalne.
newacct
2
Element ype std :: vector <T> nie musi być domyślnie konstruowalny. Możesz napisać struct A {A (int); prywatny: A (); }; wektor <A> a; w porządku - o ile nie używasz takich metod, które wymagają domyślnej możliwości budowy (np. v.resize (100); - zamiast tego musisz zrobić v.resize (100, A (1));)
Johannes Schaub - litb
A jak w takim przypadku napisałbyś taką funkcję push_back ()? Nadal będzie używać przypisania, a nie konstrukcji.
James Curran
3
James Curran, Nie ma tam domyślnej budowy. push_back just placement-news A do wstępnie przydzielonego bufora. Zobacz tutaj: stackoverflow.com/questions/672352/… . Zauważ, że moim twierdzeniem jest tylko to, że wektor może obsługiwać typy niemożliwe do domyślnego konstruowania. Nie twierdzę oczywiście, że poradziłby sobie z T & (oczywiście nie może).
Johannes Schaub - litb
29

Ion Todirel już wspomniano odpowiedź TAK użyciu std::reference_wrapper. Od wersji C ++ 11 mamy mechanizm pobierania obiektu std::vector i usuwania referencji za pomocą std::remove_reference. Poniżej podano przykład skompilowany przy użyciu g++iz clangopcją
-std=c++11i wykonany pomyślnie.

#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
źródło
12
Nie widzę tu wartości std::remove_reference<>. Chodzi o std::remove_reference<>to, aby pozwolić ci napisać „typ T, ale bez bycia referencją, jeśli tak jest”. To std::remove_reference<MyClass&>::typejest tak samo jak pisanie MyClass.
alastair
2
Nie ma w tym żadnej wartości - możesz po prostu napisać for (MyClass obj3 : vec) std::cout << obj3.getval() << "\n"; (lub for (const MyClass& obj3: vec)jeśli zadeklarujesz getval()const, tak jak powinieneś).
Toby Speight
14

boost::ptr_vector<int> będzie działać.

Edycja: była sugestią użycia std::vector< boost::ref<int> >, która nie będzie działać, ponieważ nie można domyślnie skonstruować pliku boost::ref.

Drew Dormann
źródło
8
Ale możesz mieć wektor lub typy, które nie mogą zostać zbudowane domyślnie, prawda? Trzeba tylko uważać, aby nie używać domyślnego ctor. wektora
Manuel
1
@Manuel: Or resize.
Wyścigi lekkości na orbicie
5
Uważaj, pojemniki ze wskaźnikami Boost przejmują wyłączną własność pointee. Cytat : „Gdy potrzebujesz wspólnej semantyki, ta biblioteka nie jest tym, czego potrzebujesz”.
Matthäus Brandl
12

Jest to usterka w języku C ++. Nie możesz pobrać adresu referencji, ponieważ próba zrobienia tego spowodowałaby adres obiektu, do którego się odnosi, a zatem nigdy nie możesz uzyskać wskaźnika do referencji. std::vectorwspółpracuje ze wskaźnikami do jego elementów, więc przechowywane wartości muszą być możliwe do wskazania. Zamiast tego będziesz musiał użyć wskaźników.

Adam Rosenfield
źródło
Myślę, że można go zaimplementować przy użyciu bufora void * i umieszczenia nowego. Nie żeby to miało sens.
peterchen
55
„Wada w języku” jest zbyt silna. To jest z założenia. Nie sądzę, że jest wymagane, aby wektor pracował ze wskaźnikami do elementów. Wymagana jest jednak możliwość przypisania elementu. Referencje nie są.
Brian Neal
Nie możesz też wziąć sizeofodniesienia.
David Schwartz
1
nie potrzebujesz wskaźnika do odwołania, masz odniesienie, jeśli potrzebujesz wskaźnika, po prostu trzymaj sam wskaźnik; nie ma tu problemu do rozwiązania
Ion Todirel
10

TL; DR

Użyj w std::reference_wrapperten sposób:

#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

Długa odpowiedź

Jako standard ten sugeruje , dla standardowego kontenera Xzawierającego obiekty typu T, Tmoże być Erasablez X.

Erasable oznacza, że ​​następujące wyrażenie jest dobrze uformowane:

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

Ajest typem alokatora kontenera, mjest instancją alokatora i pjest wskaźnikiem typu *T. Patrz tutaj dla Erasabledefinicji.

Domyślnie std::allocator<T>jest używany jako alokator wektora. W domyślnym alokatorze wymaganie jest równoważne z ważnością p->~T()(Uwaga: Tjest to typ odwołania i pjest wskaźnikiem do odwołania). Jednak wskaźnik do odwołania jest nielegalny , dlatego wyrażenie nie jest dobrze uformowane.

ivaigult
źródło
3

Jak już wspomnieli inni, prawdopodobnie zamiast tego użyjesz wektora wskaźników.

Możesz jednak rozważyć użycie ptr_vector !

Martin Cote
źródło
4
Ta odpowiedź nie jest wykonalna, ponieważ ptr_vector ma być pamięcią. Oznacza to, że usuwają wskaźniki przy usuwaniu. Więc nie nadaje się do jego celu.
Klemens Morgenstern
0

Jak sugerują inne komentarze, ograniczasz się do używania wskaźników. Ale jeśli to pomaga, oto jedna technika, aby uniknąć bezpośredniego spojrzenia na wskaźniki.

Możesz zrobić coś takiego:

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
źródło
reinterpret_castnie jest potrzebny
Xeverous
1
Jak pokazują inne odpowiedzi, wcale nie ograniczamy się do używania wskaźników.
underscore_d