Почему я не могу сделать вектор ссылок?

351

Когда я делаю это:

std::vector<int> hello;

Все отлично работает. Однако, когда я делаю это вектор ссылок вместо этого:

std::vector<int &> hello;

Я получаю ужасные ошибки, такие как

ошибка C2528: указатель: указатель на ссылку недопустим

Я хочу поместить несколько ссылок на структуры в вектор, чтобы мне не приходилось вмешиваться в указатели. Почему вектор вызывает истерику по этому поводу? Является ли мой единственный вариант использовать вместо него вектор указателей?

Colen
источник
46
Вы можете использовать std :: vector <reference_wrapper <int>> hello; См informit.com/guides/content.aspx?g=cplusplus&seqNum=217
Амит
2
@amit ссылка больше не действительна, официальная документация здесь
Martin

Ответы:

339

Тип компонента контейнеров, таких как векторы, должен быть назначаемым . Ссылки не могут быть назначены (вы можете инициализировать их только один раз, когда они объявлены, и вы не можете заставить их ссылаться на что-то другое позже). Другие не присваиваемые типы также не допускаются в качестве компонентов контейнеров, например vector<const int>, не допускаются.

newacct
источник
1
Вы говорите, что у меня не может быть вектора векторов? (Я уверен, что я сделал это ...)
Джеймс Керран
8
Да, std :: vector <std :: vector <int>> является правильным, std :: vector можно назначать.
Мартин Кот
17
Действительно, это и есть «настоящая» причина. Ошибка в том, что T * невозможна для T, является U & просто побочным эффектом нарушенного требования, что T должен быть назначаемым. Если бы vector смог точно проверить параметр типа, то он, вероятно, сказал бы «нарушенное требование: T & not assignable»
Йоханнес Шауб - litb
2
Проверка назначаемой концепции на boost.org/doc/libs/1_39_0/doc/html/Assignable.html все операции, кроме подкачки, действительны для ссылок.
Амит
7
Это больше не правда. Начиная с C ++ 11, единственное независимое от операции требование к элементу должно быть «стираемым», а ссылка - нет. См. Stackoverflow.com/questions/33144419/… .
laike9m
119

да, вы можете искать std::reference_wrapper, имитирующий ссылку, но назначаемый, а также можно «перезаписать»

Ион Тодирель
источник
4
Есть ли способ обойти вызов get()первым при попытке доступа к методу экземпляра класса в этой оболочке? Например reference_wrapper<MyClass> my_ref(...); my_ref.get().doStuff();, не очень ссылка, как.
Timdiels
3
Разве он не может быть преобразован в сам тип, возвращая ссылку?
WorldSEnder
3
Да, но это требует контекста, который подразумевает, какое преобразование требуется. Доступ члена не делает этого, следовательно, необходимость .get(). Что timdiels хочет operator.; взгляните на последние предложения / обсуждения по этому вопросу.
underscore_d
31

По своей природе ссылки могут быть установлены только во время их создания; то есть следующие две строки имеют очень разные эффекты:

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

Кроме того, это незаконно:

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

Однако, когда вы создаете вектор, нет способа присвоить значения его элементам при создании. По сути, вы просто делаете целую кучу последнего примера.

Джеймс Керран
источник
10
«когда вы создаете вектор, нет способа присвоить значения его элементам при создании». Я не понимаю, что вы подразумеваете под этим утверждением. Что такое "его предметы при создании"? Я могу создать пустой вектор. И я могу добавлять элементы с помощью .push_back (). Вы просто указываете, что ссылки не являются конструируемыми по умолчанию. Но я определенно могу иметь векторы классов, которые не конструируются по умолчанию.
newacct
3
Элемент ype из std :: vector <T> не обязательно должен быть конструируемым по умолчанию. Вы можете написать struct A {A (int); частный: A (); }; вектор <A> a; просто отлично - до тех пор, пока вы не используете такие методы, которые требуют, чтобы он был конструируемым по умолчанию (например, v.resize (100); - но вместо этого вам нужно будет делать v.resize (100, A (1));)
Йоханнес Шауб - Litb
И как бы вы написали такой push_back () в этом случае? Он по-прежнему будет использовать назначение, а не конструкцию.
Джеймс Керран
4
Джеймс Керран, Там нет строительства по умолчанию. push_back только размещение-новости A в предварительно выделенный буфер. Смотрите здесь: stackoverflow.com/questions/672352/… . Обратите внимание, что я утверждаю только, что vector может обрабатывать не-конструируемые по умолчанию типы. Я не утверждаю, конечно, что он мог бы справиться с T & (это не может, конечно).
Йоханнес Шауб -
29

Ион Тодирель уже упоминал ответ ДА, используя std::reference_wrapper. Начиная с C ++ 11 у нас есть механизм для извлечения объекта std::vector и удаления ссылки с помощью std::remove_reference. Ниже приведен пример, скомпилированный с использованием g++и clangс опцией
-std=c++11и успешно выполненный.

#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
источник
12
Я не вижу значения std::remove_reference<>здесь. Смысл в std::remove_reference<>том, чтобы позволить вам написать «тип T, но без ссылки, если он один». Так std::remove_reference<MyClass&>::typeже, как писать MyClass.
alastair
2
В этом нет никакого значения - вы можете просто написать for (MyClass obj3 : vec) std::cout << obj3.getval() << "\n"; (или, for (const MyClass& obj3: vec)если вы объявите getval()const, как вы должны).
Тоби Спейт
14

boost::ptr_vector<int> будет работать.

Редактировать: было предложено использовать std::vector< boost::ref<int> >, которое не будет работать, потому что вы не можете создать по умолчанию a boost::ref.

Дрю Дорманн
источник
8
Но у вас могут быть векторные или не конструируемые по умолчанию типы, верно? Вам нужно только быть осторожным, чтобы не использовать ctor по умолчанию. вектора
Мануэль
1
@ Мануэль: Или resize.
Гонки легкости на орбите
5
Будьте осторожны, контейнеры указателей Boost берут на себя исключительное право владения пуантами. Цитата : «Когда вам нужна общая семантика, эта библиотека - не то, что вам нужно».
Маттеус Брандл
12

Это недостаток языка C ++. Вы не можете взять адрес ссылки, так как попытка сделать это приведет к адресу объекта, на который ссылаются, и, таким образом, вы никогда не сможете получить указатель на ссылку. std::vectorработает с указателями на его элементы, поэтому на хранимые значения нужно указывать. Вы должны будете использовать указатели вместо этого.

Адам Розенфилд
источник
Я думаю, что это может быть реализовано с использованием буфера void * и размещения новых. Не то чтобы это имело смысл.
peterchen
55
«Ошибка в языке» слишком сильна. Это по замыслу. Я не думаю, что требуется, чтобы вектор работал с указателями на элементы. Однако требуется, чтобы элемент был назначаемым. Ссылки не являются.
Брайан Нил
Вы не можете взять sizeofссылку тоже.
Дэвид Шварц
1
вам не нужен указатель на ссылку, у вас есть ссылка, если вам нужен указатель, просто оставьте сам указатель; здесь нет проблем, которые нужно решить
Ион Тодирел
10

TL; DR

Используйте std::reference_wrapperкак это:

#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

Длинный ответ

Как предполагает стандарт , для стандартного контейнера, Xсодержащего объекты типа T, Tдолжен быть Erasableот X.

Erasable означает, что следующее выражение правильно сформировано:

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

Aявляется типом распределителя контейнера, mявляется экземпляром распределителя и pявляется указателем типа *T. Смотрите здесь для Erasableопределения.

По умолчанию std::allocator<T>используется как распределитель вектора. При использовании распределителя по умолчанию требование эквивалентно действительности p->~T()(обратите внимание, что Tэто ссылочный тип и pуказатель на ссылку). Однако указатель на ссылку недопустим , поэтому выражение сформировано некорректно .

ivaigult
источник
3

Как уже упоминалось, вы, вероятно, в конечном итоге будете использовать вектор указателей.

Тем не менее, вы можете использовать ptr_vector !

Мартин Кот
источник
4
Этот ответ не работает, поскольку ptr_vector должен быть хранилищем. То есть он удалит указатели при удалении. Так что это не годится для его цели.
Клеменс Моргенштерн
0

Как показывают другие комментарии, вы ограничены использованием указателей. Но если это помогает, вот одна из техник, чтобы избежать прямого столкновения с указателями.

Вы можете сделать что-то вроде следующего:

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_castне нужен
Xeverous
1
Как показывают другие ответы, мы не ограничены использованием указателей вообще.
underscore_d