¿Por qué no puedo hacer un vector de referencias?


351

Cuando hago esto:

std::vector<int> hello;

Todo funciona muy bien. Sin embargo, cuando lo convierto en un vector de referencias en su lugar:

std::vector<int &> hello;

Recibo errores horribles como

error C2528: 'puntero': el puntero a referencia es ilegal

Quiero poner un montón de referencias a estructuras en un vector, para no tener que entrometerme con punteros. ¿Por qué el vector está haciendo un berrinche sobre esto? ¿Es mi única opción usar un vector de punteros en su lugar?


46
puede usar std :: vector <reference_wrapper <int>> hello; Ver informit.com/guides/content.aspx?g=cplusplus&seqNum=217
amit

2
@amit el enlace ya no es válido, documentación oficial aquí
Martin

Respuestas:


339

El tipo de componente de los contenedores, como los vectores, debe ser asignable . Las referencias no son asignables (solo puede inicializarlas una vez cuando se declaran, y no puede hacer que hagan referencia a otra cosa más adelante). Otros tipos no asignables tampoco están permitidos como componentes de contenedores, por ejemplo, vector<const int>no está permitido.


1
¿Estás diciendo que no puedo tener un vector de vectores? (Estoy seguro de que he hecho eso ...)
James Curran

8
Sí, un std :: vector <std :: vector <int>> es correcto, std :: vector es asignable.
Martin Cote

17
De hecho, esta es la razón "real". El error acerca de que T * es imposible de T es U y es solo un efecto secundario del requisito violado de que T debe ser asignable. Si el vector pudo verificar con precisión el parámetro de tipo, entonces probablemente diría "requisito violado: T & no asignable"
Johannes Schaub - litb

2
Verificando el concepto asignable en boost.org/doc/libs/1_39_0/doc/html/Assignable.html todas las operaciones excepto el intercambio son válidas en las referencias.
amit

77
Esto ya no es verdad. Desde C ++ 11, el único requisito de elemento independiente de la operación es ser "borrable", y la referencia no lo es. Ver stackoverflow.com/questions/33144419/… .
laike9m

119

Sí, puede, busque std::reference_wrapper, que imita una referencia pero es asignable y también puede ser "reubicado"


44
¿Hay alguna forma de evitar las llamadas get()primero al intentar acceder a un método de una instancia de una clase en este contenedor? Por ejemplo, reference_wrapper<MyClass> my_ref(...); my_ref.get().doStuff();no es muy referencia como.
Timdiels

3
¿No se puede convertir implícitamente al Tipo en sí mismo al devolver la referencia?
WorldSEnder

3
Sí, pero eso requiere un contexto que implique qué conversión se requiere. El acceso de los miembros no hace eso, de ahí la necesidad de hacerlo .get(). Lo que Timdiels quiere es operator.; Eche un vistazo a las últimas propuestas / discusiones sobre eso.
underscore_d

31

Por su propia naturaleza, las referencias solo se pueden establecer en el momento en que se crean; es decir, las siguientes dos líneas tienen efectos muy diferentes:

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

Además, esto es ilegal:

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

Sin embargo, cuando crea un vector, no hay forma de asignar valores a sus elementos en la creación. Básicamente solo estás haciendo un montón del último ejemplo.


10
"cuando crea un vector, no hay forma de asignar valores a sus elementos en la creación" No entiendo lo que quiere decir con esta declaración. ¿Cuáles son "sus artículos en la creación"? Puedo crear un vector vacío. Y puedo agregar elementos con .push_back (). Solo está señalando que las referencias no son construibles por defecto. Pero definitivamente puedo tener vectores de clases que no son construibles por defecto.
newacct

3
No se requiere que el elemento ype de std :: vector <T> sea por defecto construible. Puede escribir struct A {A (int); privado: A (); }; vector <A> a; bien, siempre y cuando no utilice dichos métodos que requieren que sea constructible por defecto (como v.resize (100); - pero en su lugar deberá hacer v.resize (100, A (1));)
Johannes Schaub - litb

¿Y cómo escribirías un push_back () en este caso? Seguirá utilizando asignación, no construcción.
James Curran

44
James Curran, No se realiza ninguna construcción predeterminada allí. push_back solo ubicación-noticias A en el búfer preasignado. Ver aquí: stackoverflow.com/questions/672352/… . Tenga en cuenta que mi reclamo es solo que el vector puede manejar tipos construibles no predeterminados. No pretendo, por supuesto, que pueda manejar T & (no puede, por supuesto).
Johannes Schaub - litb

29

Ion Todirel ya mencionó una respuesta usando std::reference_wrapper. Desde C ++ 11 tenemos un mecanismo para recuperar objetos std::vector y eliminar la referencia mediante el uso std::remove_reference. A continuación se muestra un ejemplo compilado usando g++y clangcon la opción
-std=c++11y ejecutado con éxito.

#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
No veo el valor std::remove_reference<>aquí. El punto std::remove_reference<>es permitirle escribir "el tipo T, pero sin ser una referencia si es uno". Entonces std::remove_reference<MyClass&>::typees lo mismo que escribir MyClass.
alastair

2
No hay ningún valor en eso: solo puede escribir for (MyClass obj3 : vec) std::cout << obj3.getval() << "\n"; (o for (const MyClass& obj3: vec)si declara getval()const, como debería).
Toby Speight

14

boost::ptr_vector<int> trabajará.

Editar: fue una sugerencia de uso std::vector< boost::ref<int> >, que no funcionará porque no se puede construir por defecto a boost::ref.


8
Pero puedes tener un vector o tipos no construibles por defecto, ¿verdad? Solo debe tener cuidado de no utilizar el ctor predeterminado. del vector
Manuel

1
@Manuel: O resize.
Carreras ligeras en órbita el

55
Tenga cuidado, los contenedores de puntero de Boost son propiedad exclusiva de los pointees. Cita : "Cuando necesita semántica compartida, esta biblioteca no es lo que necesita".
Matthäus Brandl

12

Es una falla en el lenguaje C ++. No puede tomar la dirección de una referencia, ya que intentar hacerlo daría lugar a la dirección del objeto al que se hace referencia y, por lo tanto, nunca puede obtener un puntero a una referencia. std::vectorfunciona con punteros a sus elementos, por lo que los valores que se almacenan deben poder señalarse. Tendrás que usar punteros en su lugar.


Supongo que podría implementarse usando un búfer void * y una nueva ubicación. No es que esto tenga mucho sentido.
peterchen

55
"Defecto en el idioma" es demasiado fuerte. Es por diseño. No creo que sea necesario que el vector funcione con punteros a elementos. Sin embargo, se requiere que el elemento sea asignable. Las referencias no lo son.
Brian Neal

Tampoco puedes tomar sizeofuna referencia.
David Schwartz

1
no necesita un puntero a una referencia, tiene la referencia, si necesita el puntero, simplemente mantenga el puntero en sí; no hay problema para resolver aquí
Ion Todirel

10

TL; DR

Usar std::reference_wrapperasí:

#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

Respuesta larga

Como sugiere el estándar , para un contenedor estándar que Xcontiene objetos de tipo T, Tdebe ser Erasablede X.

Erasable significa que la siguiente expresión está bien formada:

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

Aes el tipo de asignador del contenedor, mes la instancia del asignador y pes un puntero de tipo *T. Ver aquí para la Erasabledefinición.

Por defecto, std::allocator<T>se utiliza como asignador de vectores. Con el asignador predeterminado, el requisito es equivalente a la validez de p->~T()(Tenga en cuenta que Tes un tipo de referencia y papunta a una referencia). Sin embargo, el puntero a una referencia es ilegal , por lo tanto, la expresión no está bien formada.


3

Como otros han mencionado, probablemente terminarás usando un vector de punteros en su lugar.

Sin embargo, ¡puede considerar usar un ptr_vector en su lugar!


44
Esta respuesta no es viable, ya que se supone que un ptr_vector es un almacenamiento. Es decir, eliminará los punteros en la eliminación. Por lo tanto, no es utilizable para su propósito.
Klemens Morgenstern

0

Como sugieren los otros comentarios, está limitado a usar punteros. Pero si ayuda, aquí hay una técnica para evitar enfrentar directamente con punteros.

Puedes hacer algo como lo siguiente:

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_castno es necesario
Xeverous

1
Como muestran las otras respuestas, no estamos limitados a usar punteros en absoluto.
underscore_d
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.