Por que não consigo fazer um vetor de referências?

351

Quando eu faço isso:

std::vector<int> hello;

Tudo funciona muito bem. No entanto, quando eu faço um vetor de referências:

std::vector<int &> hello;

Eu recebo erros horríveis como

erro C2528: 'ponteiro': o ponteiro para referência é ilegal

Quero colocar um monte de referências a estruturas em um vetor, para não precisar me intrometer com ponteiros. Por que o vetor está fazendo uma birra sobre isso? Minha única opção é usar um vetor de ponteiros?

Colen
fonte
46
você pode usar std :: vector <reference_wrapper <int>> hello; Veja informit.com/guides/content.aspx?g=cplusplus&seqNum=217
amit
2
@amit o link não é mais válido, documentação oficial aqui
Martin

Respostas:

339

O tipo de componente de contêineres como vetores deve ser atribuível . As referências não são atribuíveis (você só pode inicializá-las uma vez quando são declaradas e não pode fazer com que elas façam referência a outra coisa posteriormente). Outros tipos não atribuíveis também não são permitidos como componentes de contêineres, por exemplo, vector<const int>não é permitido.

newacct
fonte
11
Você está dizendo que não posso ter um vetor de vetores? (Eu tenho certeza que eu fiz isso ...)
James Curran
8
Sim, um std :: vector <std :: vector <int>> está correto, std :: vector é atribuível.
Martin Cote
17
De fato, essa é a razão "real". O erro de T * ser impossível de T é U & é apenas um efeito colateral do requisito violado de que T deve ser atribuível. Se o vetor fosse capaz de verificar com precisão o parâmetro type, provavelmente diria "requisito violado: T e não atribuível"
Johannes Schaub - litb
2
Verificando o conceito atribuível em boost.org/doc/libs/1_39_0/doc/html/Assignable.html todas as operações, exceto a troca, são válidas nas referências.
187/09
7
Isso não é mais verdade. Desde o C ++ 11, o único requisito independente de operação para o elemento é ser "Apagável" e a referência não. Consulte stackoverflow.com/questions/33144419/… .
laike9m
119

sim, você pode procurar std::reference_wrapperque imita uma referência, mas é atribuível e também pode ser "recolocado"

Ion Todirel
fonte
4
Existe uma maneira de contornar a chamada get()primeiro ao tentar acessar o método de uma instância de uma classe neste wrapper? Por exemplo, reference_wrapper<MyClass> my_ref(...); my_ref.get().doStuff();não é muito referência como.
timdiels
3
Não é implacitamente convertível para o próprio tipo retornando a referência?
WorldSEnder
3
Sim, mas isso requer um contexto que implica qual conversão é necessária. O acesso dos membros não faz isso, daí a necessidade .get(). O que os timdiels querem é operator.; dê uma olhada nas últimas propostas / discussões sobre isso.
Underscore_d
31

Por sua própria natureza, as referências só podem ser definidas no momento em que são criadas; ou seja, as duas linhas a seguir têm efeitos muito diferentes:

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

Além disso, isso é ilegal:

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

No entanto, quando você cria um vetor, não há como atribuir valores a seus itens na criação. Você está essencialmente apenas criando um monte do último exemplo.

James Curran
fonte
10
"quando você cria um vetor, não há como atribuir valores a seus itens na criação" Eu não entendo o que você quer dizer com esta declaração. O que são "seus itens na criação"? Eu posso criar um vetor vazio. E posso adicionar itens com .push_back (). Você está apenas apontando que as referências não são construtíveis por padrão. Mas definitivamente posso ter vetores de classes que não são construtíveis por padrão.
Newacct
2
O elemento ype do std :: vector <T> não precisa ser construtível por padrão. Você pode escrever struct A {A (int); privado: A (); }; vetor <A> a; muito bem - contanto que você não use métodos que exijam que seja construtível por padrão (como v.resize (100); - mas, em vez disso, você precisará executar v.resize (100, A (1));)
Johannes Schaub - litb 28/05
E como você escreveria um push_back () nesse caso? Ele ainda usará atribuição, não construção.
2826 James Curran
3
James Curran, Nenhuma construção padrão ocorre lá. push_back apenas placement-news A no buffer pré-alocado. Veja aqui: stackoverflow.com/questions/672352/… . Observe que minha afirmação é apenas que o vetor pode lidar com tipos não construtíveis por padrão. Não afirmo, é claro, que ele possa lidar com T& (não pode, é claro).
Johannes Schaub - litb 28/05
29

Ion Todirel já mencionou uma resposta YES usando std::reference_wrapper. Desde o C ++ 11 , temos um mecanismo para recuperar o objeto std::vector e remover a referência usando std::remove_reference. Abaixo é dado um exemplo compilado usando g++e clangcom a opção
-std=c++11e executado com sucesso.

#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
fonte
12
Não vejo o valor std::remove_reference<>aqui. O objetivo de std::remove_reference<>é permitir que você escreva "o tipo T, mas sem ser uma referência, se for um". O mesmo std::remove_reference<MyClass&>::typeacontece com a escrita MyClass.
Alastair
2
Nenhum valor nisso - você pode simplesmente escrever for (MyClass obj3 : vec) std::cout << obj3.getval() << "\n"; (ou for (const MyClass& obj3: vec)se você declarar getval()const, como deveria).
Toby Speight
14

boost::ptr_vector<int> vai funcionar.

Editar: foi uma sugestão a ser usada std::vector< boost::ref<int> >, que não funcionará porque você não pode construir por padrão a boost::ref.

Drew Dormann
fonte
8
Mas você pode ter um vetor ou tipos não construtíveis por padrão, certo? Você só precisa ter cuidado para não usar o controlador padrão. do vetor
Manuel
11
@Manuel: Or resize.
Lightness Races in Orbit
5
Tenha cuidado, os contêineres de ponteiro da Boost assumem a propriedade exclusiva dos ponteiros. Citação : "Quando você precisa de semântica compartilhada, esta biblioteca não é o que você precisa."
Matthäus Brandl
12

É uma falha na linguagem C ++. Você não pode usar o endereço de uma referência, pois a tentativa resultaria na referência ao endereço do objeto e, portanto, você nunca poderá obter um ponteiro para uma referência. std::vectortrabalha com ponteiros para seus elementos, portanto, os valores que estão sendo armazenados precisam poder ser apontados. Você precisará usar ponteiros.

Adam Rosenfield
fonte
Eu acho que poderia ser implementado usando um buffer void * e um novo posicionamento. Não que isso faça muito sentido.
Peterchen
55
"Falha na linguagem" é muito forte. É por design. Eu não acho que é necessário que o vetor funcione com ponteiros para elementos. No entanto, é necessário que o elemento seja atribuível. Referências não são.
27609 Brian Neal
Você também não pode usar a sizeofreferência a.
David Schwartz
11
você não precisa de um ponteiro para uma referência, você tem a referência; se você precisar do ponteiro, mantenha o ponteiro em si; não há nenhum problema a ser resolvido aqui
Ion Todirel 3/17/17
10

TL; DR

Use std::reference_wrapperassim:

#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

Resposta longa

Como o padrão sugere , para um contêiner padrão Xcontendo objetos do tipo T, Tdeve ser Erasablede X.

Erasable significa que a seguinte expressão está bem formada:

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

Aé alocador tipo de recipiente, mé exemplo alocador e pé um ponteiro de tipo *T. Veja aqui paraErasable definição.

Por padrão, std::allocator<T>é usado como alocador de vetor. Com o alocador padrão, o requisito é equivalente à validade de p->~T()(Observe que Té um tipo de referência e pé ponteiro para uma referência). No entanto, o ponteiro para uma referência é ilegal , portanto, a expressão não está bem formada.

ivaigult
fonte
3

Como já mencionado, você provavelmente acabará usando um vetor de ponteiros.

No entanto, você pode considerar o uso de um ptr_vector !

Martin Cote
fonte
4
Esta resposta não é viável, pois um ptr_vector deve ser um armazenamento. Ou seja, ele excluirá os ponteiros na remoção. Portanto, não é utilizável para o seu propósito.
Klemens Morgenstern
0

Como os outros comentários sugerem, você está limitado a usar ponteiros. Mas se ajudar, aqui está uma técnica para evitar o enfrentamento direto de ponteiros.

Você pode fazer algo como o seguinte:

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
fonte
reinterpret_castnão é necessário
Xeverous
11
Como mostram as outras respostas, não estamos limitados a usar ponteiros.
Underscore_d