Pourquoi ne puis-je pas faire un vecteur de références?

351

Quand je fais ça:

std::vector<int> hello;

Tout fonctionne très bien. Cependant, lorsque j'en fais plutôt un vecteur de références:

std::vector<int &> hello;

J'ai des erreurs horribles comme

erreur C2528: 'pointeur': le pointeur vers la référence est illégal

Je veux mettre un tas de références à des structures dans un vecteur, afin de ne pas avoir à me mêler de pointeurs. Pourquoi le vecteur fait-il une crise de colère à ce sujet? Est-ce ma seule option pour utiliser un vecteur de pointeurs à la place?

Colen
la source
46
vous pouvez utiliser std :: vector <reference_wrapper <int>> bonjour; Voir informit.com/guides/content.aspx?g=cplusplus&seqNum=217
Amit
2
@amit le lien n'est plus valide, documentation officielle ici
Martin

Réponses:

339

Le type de composant des conteneurs comme les vecteurs doit être assignable . Les références ne sont pas assignables (vous ne pouvez les initialiser qu'une seule fois lorsqu'elles sont déclarées et vous ne pouvez pas les faire référencer autre chose plus tard). D'autres types non attribuables ne sont pas non plus autorisés en tant que composants de conteneurs, par exemple vector<const int>n'est pas autorisé.

newacct
la source
1
Voulez-vous dire que je ne peux pas avoir de vecteur de vecteurs? (Je suis sûr que je l'ai fait ...)
James Curran
8
Oui, un std :: vector <std :: vector <int>> est correct, std :: vector est assignable.
Martin Cote
17
En effet, c'est la raison "réelle". L'erreur à propos de l'impossibilité de T * de T est U & n'est qu'un effet secondaire de l'exigence violée selon laquelle T doit être attribuable. Si le vecteur était capable de vérifier avec précision le paramètre de type, il dirait probablement "exigence non respectée: T et non attribuable"
Johannes Schaub - litb
2
En vérifiant le concept assignable sur boost.org/doc/libs/1_39_0/doc/html/Assignable.html toutes les opérations sauf le swap sont valides sur les références.
Amit
7
Ce n'est plus vrai. Depuis C ++ 11, la seule exigence indépendante de l'opération pour l'élément doit être "Effaçable", et la référence ne l'est pas. Voir stackoverflow.com/questions/33144419/… .
laike9m
119

oui vous pouvez, chercher std::reference_wrapper, qui imite une référence mais est assignable et peut également être "réinstallé"

Ion Todirel
la source
4
Existe-t-il un moyen de contourner l'appel en get()premier lorsque vous essayez d'accéder à une méthode d'une instance d'une classe dans ce wrapper? Par exemple, ce reference_wrapper<MyClass> my_ref(...); my_ref.get().doStuff();n'est pas une référence très semblable.
timdiels
3
N'est-il pas implicitement transposable au type lui-même en renvoyant la référence?
WorldSEnder
3
Oui, mais cela nécessite un contexte qui implique la conversion requise. L'accès des membres ne fait pas cela, d'où la nécessité .get(). Ce que veut timdiels, c'est operator.; jetez un œil aux dernières propositions / discussions à ce sujet.
underscore_d
31

De par leur nature même, les références ne peuvent être établies qu'au moment de leur création; c'est-à-dire que les deux lignes suivantes ont des effets très différents:

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

En plus, c'est illégal:

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

Cependant, lorsque vous créez un vecteur, il n'y a aucun moyen d'attribuer des valeurs à ses éléments lors de la création. Vous faites essentiellement tout un tas du dernier exemple.

James Curran
la source
10
"lorsque vous créez un vecteur, il n'y a aucun moyen d'attribuer des valeurs à ses éléments lors de la création" Je ne comprends pas ce que vous entendez par cette déclaration. Quels sont "ses articles à la création"? Je peux créer un vecteur vide. Et je peux ajouter des éléments avec .push_back (). Vous faites juste remarquer que les références ne sont pas constructibles par défaut. Mais je peux certainement avoir des vecteurs de classes qui ne sont pas constructibles par défaut.
newacct
3
L'élément ype de std :: vector <T> n'est pas obligatoire pour être constructible par défaut. Vous pouvez écrire la structure A {A (int); privé: A (); }; vecteur <A> a; très bien - tant que vous n'utilisez pas de telles méthodes qui nécessitent qu'il soit constructible par défaut (comme v.resize (100); - mais à la place, vous devrez faire v.resize (100, A (1));)
Johannes Schaub - litb
Et comment écririez-vous un tel push_back () dans ce cas? Il utilisera toujours l'affectation, pas la construction.
James Curran
4
James Curran, Aucune construction par défaut n'y a lieu. push_back juste placement-news A dans le tampon préalloué. Voir ici: stackoverflow.com/questions/672352/… . Notez que ma revendication est seulement que le vecteur peut gérer les types constructibles non par défaut. Je ne prétends pas, bien sûr, qu'il pourrait gérer T & (il ne peut pas, bien sûr).
Johannes Schaub - litb
29

Ion Todirel a déjà mentionné une réponse OUI en utilisant std::reference_wrapper. Depuis C ++ 11, nous avons un mécanisme pour récupérer l'objet std::vector et supprimer la référence en utilisant std::remove_reference. Ci-dessous est donné un exemple compilé en utilisant g++et clangavec option
-std=c++11et exécuté avec succès.

#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
la source
12
Je ne vois pas la valeur std::remove_reference<>ici. Le but std::remove_reference<>est de vous permettre d'écrire "le type T, mais sans être une référence s'il en est un". Il en std::remove_reference<MyClass&>::typeva de même pour l'écriture MyClass.
alastair
2
Aucune valeur du tout - vous pouvez simplement écrire for (MyClass obj3 : vec) std::cout << obj3.getval() << "\n"; (ou for (const MyClass& obj3: vec)si vous déclarez getval()const, comme vous le devriez).
Toby Speight
14

boost::ptr_vector<int> marchera.

Edit: était une suggestion à utiliser std::vector< boost::ref<int> >, qui ne fonctionnera pas car vous ne pouvez pas construire par défaut un boost::ref.

Drew Dormann
la source
8
Mais vous pouvez avoir un vecteur ou des types non constructibles par défaut, non? Il suffit de faire attention à ne pas utiliser le ctor par défaut. du vecteur
Manuel
1
@Manuel: Ou resize.
Courses de légèreté en orbite le
5
Soyez prudent, les conteneurs de pointeurs de Boost prennent la propriété exclusive des pointes. Citation : "Lorsque vous avez besoin d'une sémantique partagée, cette bibliothèque n'est pas ce dont vous avez besoin."
Matthäus Brandl
12

C'est une faille dans le langage C ++. Vous ne pouvez pas prendre l'adresse d'une référence, car tenter de le faire entraînerait l'adresse de l'objet auquel il est fait référence, et vous ne pouvez donc jamais obtenir un pointeur vers une référence. std::vectorfonctionne avec des pointeurs vers ses éléments, de sorte que les valeurs stockées doivent pouvoir être pointées. Vous devrez utiliser des pointeurs à la place.

Adam Rosenfield
la source
Je suppose que cela pourrait être implémenté en utilisant un tampon void * et un nouveau placement. Non pas que cela aurait beaucoup de sens.
peterchen
55
"La faille dans la langue" est trop forte. C'est par conception. Je ne pense pas qu'il soit nécessaire que le vecteur fonctionne avec des pointeurs vers des éléments. Cependant, il est nécessaire que l'élément soit assignable. Les références ne le sont pas.
Brian Neal
Vous ne pouvez pas non plus prendre la sizeofréférence a.
David Schwartz
1
vous n'avez pas besoin d'un pointeur vers une référence, vous avez la référence, si vous avez besoin du pointeur, gardez simplement le pointeur lui-même; il n'y a pas de problème à résoudre ici
Ion Todirel
10

TL; DR

Utilisez std::reference_wrappercomme ceci:

#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

Longue réponse

Comme le suggère la norme , pour un conteneur standard Xcontenant des objets de type T, Tdoit être Erasablede X.

Erasable signifie que l'expression suivante est bien formée:

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

Aest le type d'allocateur du conteneur, mest une instance d'allocateur et pest un pointeur de type *T. Voir ici pour la Erasabledéfinition.

Par défaut, std::allocator<T>est utilisé comme allocateur de vecteur. Avec l'allocateur par défaut, l'exigence est équivalente à la validité de p->~T()(Notez qu'il Ts'agit d'un type de référence et pd'un pointeur sur une référence). Cependant, le pointeur vers une référence est illégal , d'où l'expression n'est pas bien formée.

ivaigult
la source
3

Comme d'autres l'ont mentionné, vous finirez probablement par utiliser un vecteur de pointeurs à la place.

Cependant, vous voudrez peut-être envisager d'utiliser un ptr_vector à la place!

Martin Cote
la source
4
Cette réponse n'est pas réalisable, car un ptr_vector est censé être un stockage. C'est-à-dire qu'il supprimera les pointeurs lors de la suppression. Ce n'est donc pas utilisable pour son but.
Klemens Morgenstern
0

Comme les autres commentaires le suggèrent, vous êtes limité à utiliser des pointeurs. Mais si cela peut aider, voici une technique pour éviter de faire face directement aux pointeurs.

Vous pouvez faire quelque chose comme ceci:

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
la source
reinterpret_castn'est pas nécessaire
Xeverous
1
Comme le montrent les autres réponses, nous ne sommes pas du tout limités à utiliser des pointeurs.
underscore_d