Чому я не можу скласти вектор посилань?


351

Коли я це роблю:

std::vector<int> hello;

Все чудово працює. Однак, коли я перетворюю його на вектор посилань:

std::vector<int &> hello;

Я отримую жахливі помилки на кшталт

помилка C2528: 'pointer': вказівник на посилання є незаконним

Я хочу помістити купу посилань на структури у вектор, щоб мені не доводилося втручатися в покажчики. Чому вектор кидає істерику з цього приводу? Чи єдиний мій варіант замість цього використовувати вектор покажчиків?


46
ви можете використовувати std :: vector <reference_wrapper <int>> привіт; Дивіться informit.com/guides/content.aspx?g=cplusplus&seqNum=217
amit

2
@amit посилання вже не дійсна, офіційна документація тут
Мартін

Відповіді:


339

Компонентний тип контейнерів, таких як вектори, повинен бути призначений . Посилання не присвоюються (їх можна ініціалізувати лише один раз, коли вони оголошені, і ви не можете змусити їх посилатися на щось інше пізніше). Інші неприсвоювані типи також не дозволені як компоненти контейнерів, наприклад vector<const int>, заборонено.


1
Ти кажеш, я не можу мати векторів? (Я впевнений, що я це зробив ...)
Джеймс Курран

8
Так, правильний std :: вектор <std :: vector <int>>, std :: вектор може бути призначений.
Мартін Кот

17
Дійсно, це "фактична" причина. Помилка про те, що T * неможливо T є U &, є лише побічним ефектом від порушеної вимоги, яку T має бути призначено. Якщо вектору вдалося точно перевірити параметр типу, то він, ймовірно, сказав би "порушена вимога: T & не призначається"
Йоханнес Шауб - ліб

2
Перевірка присвоюваної концепції на boost.org/doc/libs/1_39_0/doc/html/Assignable.html всі операції, за винятком swap, є дійсними у посиланнях.
amit

7
Це вже не так. Оскільки C ++ 11, єдиною незалежною від роботи вимогою для елемента є "Erasable", а посилання - ні. Дивіться 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(). Чого хочуть терміни - це operator.; ознайомтеся з останніми пропозиціями / дискусіями з цього приводу.
підкреслюйте_30

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

2
Елемент ype of std :: vector <T> не повинен бути конструктованим за замовчуванням. Ви можете написати структуру A {A (int); приватний: A (); }; вектор <A> a; просто добре - до тих пір, поки ви не використовуєте такі методи, які вимагають його конструювання за замовчуванням (наприклад, v.resize (100); - але замість цього вам потрібно буде зробити v.resize (100, A (1));)
Йоханнес Шауб - ліб

І як би ви писали такий push_back () у цьому випадку? Він все одно використовуватиме призначення, а не побудову.
Джеймс Курран

3
Джеймс Курран, там будівництво за замовчуванням не відбувається. push_back просто розміщення-новини A в попередньо виділений буфер. Дивіться тут: stackoverflow.com/questions/672352 / ... . Зауважте, що моя заява полягає лише в тому, що вектор може обробляти типи, які не можуть бути сконструйовані за замовчуванням. Я, звичайно, не стверджую, що він міг би впоратися з 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";
    }             
}

12
Я не бачу тут значення std::remove_reference<>. Суть у std::remove_reference<>тому, щоб дозволити писати "тип T, але не бути посиланням, якщо він один". Так std::remove_reference<MyClass&>::typeце просто те саме, що писати MyClass.
аластер

2
В цьому немає жодної цінності - ви можете просто написати for (MyClass obj3 : vec) std::cout << obj3.getval() << "\n"; (або for (const MyClass& obj3: vec)якщо ви оголосите getval()const, як слід).
Toby Speight

14

boost::ptr_vector<int> буду працювати.

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


8
Але ти можеш мати векторні або неконструктивні типи, правда? Ви повинні бути обережними, щоб не використовувати замовчувальний ctor. вектора
Мануель,


5
Будьте уважні, контейнери для покажчиків Boost беруть ексклюзивне право власності на пуанти. Цитата : "Коли вам потрібна спільна семантика, ця бібліотека - це не те, що вам потрібно".
Маттей Брандл

12

Це недолік у мові C ++. Ви не можете прийняти адресу посилання, оскільки спроба зробити це призведе до того, що адреса об'єкта буде посилатися, і, таким чином, ви ніколи не зможете отримати вказівник на посилання. std::vectorпрацює з покажчиками на його елементи, тому значення, що зберігаються, потрібно мати можливість вказувати. Вам доведеться використовувати вказівники замість цього.


Я думаю, що це може бути реалізовано за допомогою буфера void * та розміщення new. Не те, щоб це мало особливого сенсу.
peterchen

55
"Недолік мови" занадто сильний. Це за дизайном. Я не думаю, що потрібно, щоб вектор працював з покажчиками на елементи. Однак потрібно, щоб елемент був призначений. Посилання не є.
Брайан Ніл

Ви також не можете взяти sizeofпосилання.
Девід Шварц

1
вам не потрібен вказівник на посилання, у вас є посилання, якщо вам потрібен вказівник, просто тримайте сам вказівник; тут немає жодної проблеми, яку потрібно вирішити
Іон Тодірел

10

TL; ДОКТОР

Використовуйте 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вказує на посилання). Однак вказівка ​​на посилання є незаконною , отже, вираз є недостатньо сформованим.


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
Як показують інші відповіді, ми зовсім не обмежуємось використанням покажчиків.
підкреслити_30
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.