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?


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.


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ć”


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.


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";
    }             
}

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.


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.


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.


3

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

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


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]);
}

reinterpret_castnie jest potrzebny
Xeverous

1
Jak pokazują inne odpowiedzi, wcale nie ograniczamy się do używania wskaźników.
underscore_d
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.