Чому масиви посилань незаконні?


149

Наступний код не компілюється.

int a = 1, b = 2, c = 3;
int& arr[] = {a,b,c,8};

Що говорить про це стандарт C ++?

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

struct cintref
{
    cintref(const int & ref) : ref(ref) {}
    operator const int &() { return ref; }
private:
    const int & ref;
    void operator=(const cintref &);
};

int main() 
{
  int a=1,b=2,c=3;
  //typedef const int &  cintref;
  cintref arr[] = {a,b,c,8};
}

Можна використовувати struct cintrefзамість const int &симуляції масив посилань.


1
Навіть якби масив був дійсним, зберігання в ньому сирого значення "8" не буде працювати. Якби ви зробили "intlink value = 8;", воно б жахливо померло, тому що воно майже просто переведене на "const int & value = 8;". Посилання має посилатися на змінну.
Грант Петерс

3
intlink value = 8;справді працює. перевірити, чи не віриш ти.
Олексій Малістов

7
Як зазначає Олексій, цілком справедливо прив’язати ревальвію до посилання const.
avakar

1
Що не працює, це те operator=. Посилання не можна перезавантажувати. Якщо ви дійсно хочете такої семантики - хоча я особисто не знаходив ситуації, в якій вони практично корисні - тоді це std::reference_wrapperбув би спосіб зробити це, оскільки вона фактично зберігає вказівник, але надає посилання як operatorі це дозволяє перевстановлення. Але тоді я просто скористався вказівником!
підкреслюй_d

1
Оператор = приватний і не реалізований, але те, що в C ++ 11 є = видалити.
Джиммі Хартцел

Відповіді:


148

Відповідаючи на ваше запитання щодо стандарту, я можу навести стандарт C ++ §8.3.2 / 4 :

Не повинно бути посилань на посилання, ані масивів посилань , ані покажчиків на посилання.


32
Що ще сказати?
поліглот

9
повинні бути масиви посилань з тієї ж причини, що у нас є масиви покажчиків, та сама інформація, але різного керування, ні?
neu-rah

6
Якби розробники компілятора як розширення вирішили дозволити масиви посилань, то чи буде це успіхом? Або є якісь реальні проблеми - можливо, неоднозначний код - які просто змусять заплутатися у визначенні цього розширення?
Аарон Мак-Дейд

23
@AaronMcDaid Я думаю, що справжня причина - яка настільки помітно відсутня в цьому & подібних дискусіях - полягає в тому, що якби у кожного був масив посилань, як би він, можливо, не розмежував між адресою елемента та адресою його референта? Безпосереднє заперечення "Ви не можете помістити посилання в масив / контейнер / що завгодно" полягає в тому, що ви можете поставити structєдиний член, посилання якого є. Але потім, зробивши це, ви тепер отримали і посилання, і батьківський об'єкт, щоб назвати ..., що тепер означає, що ви можете однозначно вказати, яку адресу ви хочете. Здається, очевидно ... так чому ж ніхто цього не сказав?
підкреслити_d

95
@polyglot What more is there to say?Обґрунтування того, чому ? Я все про Стандарт, але просто цитую його і припускаю, що це закінчення дискусії, здається, надійним способом знищення критичної думки користувачів - разом із будь-яким потенціалом для розвитку мови, наприклад, поза межами упущення або штучне обмеження. Занадто багато ", оскільки Стандарт каже" тут - і недостатньо ", і це дуже розумно сказати через наступні практичні причини". Я не знаю, чому звернення так гаряче відповідають відповідям, що говорять лише перші, навіть не намагаючись досліджувати останні.
підкреслюй_d

64

Посилання не є об’єктами. У них немає власного сховища, вони просто посилаються на існуючі об'єкти. З цієї причини не має сенсу мати масиви посилань.

Якщо ви хочете легкої ваги об'єкт , який посилається на інший об'єкт , то ви можете використовувати покажчик. Ви можете використовувати a structз посилальним членом як об'єкти в масивах, лише якщо ви надасте явну ініціалізацію для всіх посилальних членів для всіхstruct екземплярів. Посилання не можуть бути використані за замовчуванням.

Редагувати: Як зазначає jia3ep, у стандартному розділі про декларації існує чітка заборона на масиви посилань.


6
Посилання за своєю суттю є такими ж постійними вказівниками, і тому вони займають деяку пам’ять (сховище), щоб вказати на щось.
inazaruk

7
Не обов'язково. Компілятор, можливо, зможе уникнути збереження адреси, якщо, наприклад, це посилання на локальний об'єкт.
EFraim

4
Не обов'язково. Посилання в структурах зазвичай займають деяке сховище. Місцеві посилання часто цього не роблять. Так чи інакше, у суворому стандартному розумінні посилання не є об'єктами, і (це було для мене новим) названі посилання насправді не є змінними .
CB Bailey

26
Так, але це деталь реалізації. Об'єкт в моделі C ++ є надрукованою областю зберігання. Посилання явно не є об'єктом, і немає гарантії, що вона займає сховище в будь-якому конкретному контексті.
CB Bailey

9
Якщо говорити так: ви можете мати структуру нічого, окрім 16 відсилань до foo, і використовувати його точно так само, як я хотів би використовувати мій масив refs - за винятком того, що ви не можете індексувати в 16 посиланнях foo . Це мовна бородавка IMHO.
greggo

28

Це цікава дискусія. Очевидно, що масиви посилань прямо неправомірні, але причина ІМХО не є такою простою, як сказати "вони не є об'єктами" або "вони не мають розміру". Я зазначу, що самі масиви не є повноцінними об'єктами в C / C ++ - якщо ви заперечуєте проти цього, спробуйте створити кілька класів шаблонів stl, використовуючи масив як параметр шаблону 'class', і подивіться, що відбувається. Ви не можете їх повернути, призначити, передати їх як параметри. (параметр масиву трактується як вказівник). Але законно робити масиви масивів. Посилання мають розмір, який компілятор може і повинен обчислити - ви не можете розмістити () посилання, але ви можете створити структуру, що не містить нічого, крім посилань. Він матиме достатній розмір, щоб містити всі покажчики, які реалізують посилання. Ти можеш'

struct mys {
 int & a;
 int & b;
 int & c;
};
...
int ivar1, ivar2, arr[200];
mys my_refs = { ivar1, ivar2, arr[12] };

my_refs.a += 3  ;  // add 3 to ivar1

Насправді ви можете додати цей рядок до визначення структури

struct mys {
 ...
 int & operator[]( int i ) { return i==0?a : i==1? b : c; }
};

... і тепер у мене є щось, що виглядає багато, як масив refs:

int ivar1, ivar2, arr[200];
mys my_refs = { ivar1, ivar2, arr[12] };

my_refs[1] = my_refs[2]  ;  // copy arr[12] to ivar2
&my_refs[0];               // gives &my_refs.a == &ivar1

Тепер це не справжній масив, це перевантаження оператора; він не буде робити те, що масиви зазвичай роблять, наприклад, sizeof (arr) / sizeof (arr [0]). Але він робить саме те, що я хочу робити з масивом посилань, із цілком законним C ++. За винятком (a) больових налаштувань для більш ніж 3-х або 4-х елементів, і (b) він робить обчислення за допомогою пучка?: Що можна зробити за допомогою індексації (не з нормальною індексацією семантики C-pointer-розрахунку-семантики) , але все-таки індексація). Я хотів би бачити дуже обмежений тип "масиву посилань", який може насправді зробити це. Тобто масив посилань не трактуватиметься як загальний масив речей, які є посиланнями, а, скоріше, це буде новий "масив посилань" зробити за допомогою шаблонів).

це, мабуть, спрацює, якщо ви не заперечуєте проти такого типу противного: переробіть '* this' як масив int * 's і поверніть посилання, зроблене з одного: (не рекомендується, але це показує, як правильний' масив ' буде працювати):

 int & operator[]( int i ) { return *(reinterpret_cast<int**>(this)[i]); }

1
Це можна зробити в C ++ 11 std::tuple<int&,int&,int&> abcref = std::tie( a,b,c)- це створює "масив" посилань, який можна індексувати лише константами часу компіляції за допомогою std :: get. Але ти не можеш цього зробити std::array<int&,3> abcrefarr = std::tie( a,b,c). Можливо, є спосіб зробити це, про яке я не знаю. Мені здається, що повинен бути спосіб написати спеціалізацію, std::array<T&,N>яка може це зробити - і, таким чином, функція, std::tiearray(...)яка повертає один такий (або дозволяє std::array<T&,N>прийняти ініціалізатор, що містить адреси = {& v1, & v2 і т.д.})
greggo

Ваша пропозиція нерозумна (IMO), і ваш останній рядок передбачає, що int може містити вказівник (як правило, помилковий, принаймні там, де я працюю). Але це єдина відповідь тут із законним обґрунтуванням того, чому масиви посилань заборонені, тому +1
Немо

@Nemo це насправді не розглядалося як пропозиція, а просто для того, щоб проілюструвати, що функціональність такої речі не є абсурдною на її обличчі. В останньому коментарі я зауважую, що у c ++ є речі, які наближаються. Крім того, останній рядок, на який ви посилаєтесь, передбачає, що пам'ять, що реалізує посилання на int, може бути корисно відчужена як вказівник на вклад - структура містить посилання, а не int - і це, як правило, істинно. Я не стверджую, що це портативний (або навіть захищений від правил "невизначеної поведінки"). малося на увазі проілюструвати, як можна використовувати індексацію під кришкою, щоб уникнути всіх?:
greggo

Досить справедливо. Але я вважаю, що ця ідея "абсурдна на її обличчі", і саме тому масиви посилань заборонені. Масив - це, в першу чергу, контейнер. Усі контейнери потребують типу ітератора для застосування стандартних алгоритмів. Тип ітератора для "масиву T" зазвичай "T *". Що таке тип ітератора для масиву посилань? Обсяг мовної хакерської роботи, який потребує вирішення всіх цих питань, смішний для суттєво марної функції. Насправді, можливо, я зроблю на це свою відповідь :-)
Немо,

@Nemo Це не повинно бути частиною основної мови, тому тип ітератора, який є "на замовлення", не є великим завданням. Усі різні типи контейнерів мають свої типи ітераторів. У цьому випадку ітератор, відменений, посилався б на цільовий objs, а не на refs у масиві, що повністю відповідає поведінці масиву. Можливо, для підтримки як шаблону може знадобитися трохи нового C ++ поведінки, дуже простоstd::array<T,N> , легко визначений у коді програми, не додався до std :: до c ++ 11, імовірно, тому що бородавочно мати це не має змоги робити = {1,2,3};те , що «мова злому?
greggo

28

Прокоментуйте свою редакцію:

Краще рішення std::reference_wrapper.

Детальніше: http://www.cplusplus.com/reference/functional/reference_wrapper/

Приклад:

#include <iostream>
#include <functional>
using namespace std;

int main() {
    int a=1,b=2,c=3,d=4;
    using intlink = std::reference_wrapper<int>;
    intlink arr[] = {a,b,c,d};
    return 0;
}

Привіт, це було сказано ще в 09. Порада шукайте нові повідомлення :)
Stígandr

9
Пост старий, але не всі знають про рішення з reference_wrapper. Людям потрібно читати рух про STL та STDlib C ++ 11.
Ти

Це дає посилання та зразок коду, а не просто згадування назви класу reference_wrapper.
Девід Стоун

12

Масив неявно перетворюється на покажчик, а вказівник на посилання є незаконним у C ++


11
Це правда, ви не можете мати вказівник на посилання, але це не причина, що ви не можете мати масив посилань. Скоріше вони є обома симптомами того, що посилання не є об'єктами.
Річард Корден

1
Структура не може містити нічого, крім посилань, і вона матиме розмір, пропорційний їх кількості. Якщо у вас був масив refs 'arr', звичайне перетворення масиву в покажчик не мало б сенсу, оскільки 'pointer-to-reference' не має сенсу. але було б сенс просто використовувати arr [i], так само, як st.ref, коли 'st' є структурою, що містить ref. Хм. Але & arr [0] дасть адресу першого посилається об'єкта, а & arr [1] - & arr [0] не буде таким же, як & arr [2] - & arr [1] - це створило б багато дивацтв.
greggo

11

Дано int& arr[] = {a,b,c,8};, що таке sizeof(*arr)?

Скрізь, посилання трактується як просто сама річ, так sizeof(*arr)просто має бути sizeof(int). Але це зробило б арифметику вказівника масиву на цьому масиві помилковою (якщо припустити, що посилання не однакової ширини є ints). Щоб усунути неоднозначність, заборонено.


Так. Ви не можете мати масив чогось, якщо він не має розмір. Який розмір посилання?
Девід Шварц

4
@DavidSchwartz Складіть, structчий єдиний член - це посилання та дізнайтеся ..?
підкреслюйте_d

3
Це має бути прийнята відповідь, а не дурна педантична, яка просто кидає стандарт на ОП.
Тайсон Джейкобс

Можливо, є друкарська помилка. "припустимо, що посилання не однакові по ширині є" ints "повинно бути" припускаючи, що посилання не такі ж ширини, як ints ". Змінити це в якості .
zhenguoli

Але зачекайте, за цією логікою у вас не було змінних змінних членів.
Тайсон Джейкобс

10

Тому що, як багато хто казав тут, посилання не є об’єктами. вони просто псевдоніми. Щоправда, деякі компілятори можуть реалізовувати їх як покажчики, але стандарт цього не примушує / не вказує. А оскільки посилання не є об’єктами, ви не можете вказати на них. Зберігання елементів у масиві означає, що існує якась індексна адреса (тобто, вказівка ​​на елементи певного індексу); і тому ви не можете мати масиви посилань, тому що ви не можете вказати на них.

Використовуйте замість boost :: reference_wrapper або boost :: tuple; або просто вказівники.


5

Я вважаю, що відповідь дуже проста, і це стосується семантичних правил посилань та способів обробки масивів в C ++.

Якщо коротко: посилання можна розглядати як структури, які не мають конструктора за замовчуванням, тому застосовуються всі ті ж правила.

1) Семантично посилання не мають значення за замовчуванням. Посилання можна створити лише шляхом посилання на щось. Посилання не мають значення для відображення відсутності посилання.

2) Виділяючи масив розміром X, програма створює колекцію об'єктів, ініціалізованих за замовчуванням. Оскільки посилання не має значення за замовчуванням, створення такого масиву є семантично незаконним.

Це правило також застосовується до структур / класів, які не мають конструктора за замовчуванням. Наступний зразок коду не складається:

struct Object
{
    Object(int value) { }
};

Object objects[1]; // Error: no appropriate default constructor available

1
Ви можете створити масив Object, ви просто повинні переконатися , щоб форматувати їх усіх: Object objects[1] = {Object(42)};. Тим часом ви не можете створити масив посилань, навіть якщо ви ініціалізуєте їх.
Anton3

4

Ви можете досить близько наблизитися до цієї структури шаблонів. Однак вам потрібно ініціалізувати вирази, які є вказівниками на T, а не на T; і таким чином, хоча ви можете легко зробити "fake_constref_array" аналогічно, ви не зможете пов'язувати це з ревальваціями, як це зроблено в прикладі ОП ("8");

#include <stdio.h>

template<class T, int N> 
struct fake_ref_array {
   T * ptrs[N];
  T & operator [] ( int i ){ return *ptrs[i]; }
};

int A,B,X[3];

void func( int j, int k)
{
  fake_ref_array<int,3> refarr = { &A, &B, &X[1] };
  refarr[j] = k;  // :-) 
   // You could probably make the following work using an overload of + that returns
   // a proxy that overloads *. Still not a real array though, so it would just be
   // stunt programming at that point.
   // *(refarr + j) = k  
}

int
main()
{
    func(1,7);  //B = 7
    func(2,8);     // X[1] = 8
    printf("A=%d B=%d X = {%d,%d,%d}\n", A,B,X[0],X[1],X[2]);
        return 0;
}

-> A = 0 B = 7 X = {0,8,0}


2

Опорний об'єкт не має розміру. Якщо ви пишете sizeof(referenceVariable), він надасть вам розмір об'єкта, на який посилається referenceVariable, а не на сам посилання. Він не має власного розміру, тому компілятор не може обчислити, який розмір потрібен буде масив.


3
якщо так, то як тоді компілятор може обчислити розмір структури, що містить лише ref?
Юстер

Компілятор знає фізичний розмір посилання - він такий самий, як і вказівник. Посилання - це насправді семантично прославлені покажчики. Різниця між вказівниками та посиланнями полягає в тому, що в посиланнях розміщено досить багато семантичних правил, щоб зменшити кількість помилок, які можна створити порівняно з покажчиками.
Крістапас А.

2

Коли ви зберігаєте щось у масиві, його розмір повинен бути відомий (оскільки індексація масиву залежить від розміру). Згідно стандарту C ++ Не визначено, чи потрібна посилання для зберігання, в результаті індексація масиву посилань була б неможливою.


1
Але, поставивши вказане посилання в struct, магічно все стає уточненим, чітко визначеним, гарантованим та детермінованим розміром. Чому, отже, існує ця, здавалося б, абсолютно штучна різниця?
підкреслюй_d

... звичайно, якщо хтось насправді починає замислюватися над тим, що structдає обгортка , деякі хороші можливі відповіді починають підказувати самі - але мені, ще ніхто цього не зробив, незважаючи на те, що це одна з найближчих проблем для всіх загальних обґрунтування того, чому ви не можете використовувати розгорнуті реф.
підкреслюй_d

2

Просто додати до всієї розмови. Оскільки масиви вимагають послідовних місць пам’яті для зберігання елемента, тож якщо ми створимо масив посилань, тоді не гарантується, що вони будуть знаходитись у послідовному розташуванні пам’яті, тому доступ до них буде проблемою, а отже, ми навіть не можемо застосувати всі математичні операції на масив.


Розгляньте наведений нижче код з початкового питання: int a = 1, b = 2, c = 3; int & arr [] = {a, b, c, 8}; Існує дуже менша ймовірність того, що a, b, c буде проживати в послідовній пам'яті.
Аміт Кумар

Це далеко не головна проблема концепції зберігання посилань у контейнері.
підкреслюй_d

0

Розглянемо масив покажчиків. Вказівник - це дійсно адреса; тому, коли ви ініціалізуєте масив, ви аналогічно говорите комп'ютеру, "виділіть цей блок пам'яті для зберігання цих X-номерів (які є адресами інших елементів)". Тоді якщо ви зміните один із вказівників, ви просто змінюєте те, на що він вказує; це все ще числова адреса, яка сама сидить на тому ж місці.

Посилання є аналогічним псевдоніму. Якби ви мали оголосити масив посилань, ви, в основному, скажете комп'ютеру, "виділіть цю аморфну ​​частину пам'яті, що складається з усіх цих різних елементів, розкиданих навколо".


1
Чому, тоді, створення structєдиного члена якого є еталонним результатом у чомусь абсолютно законному, передбачуваному та неаморфному? Чому користувач повинен обробляти посилання на нього, щоб магічно здобути потужність, здатну до масиву, або це лише штучне обмеження? ІМО жодної відповіді безпосередньо не вирішив. Я припускаю, що спростування - "Об'єкт, що обгортає, гарантує, що ви можете використовувати семантику значень у вигляді об'єкта, що обгортає, тому він гарантує, що ви можете мати гарантії значень, які необхідні, тому що, наприклад, як можна розмежувати елемент і адресу судді" ... Це ви мали на увазі?
підкреслюйте_d

-2

Власне, це суміш синтаксису C і C ++.

Ви повинні або використовувати чисті масиви C, які не можуть бути посилання, так як посилання є частиною C ++ тільки. Або ви йдете шляхом C ++ і використовуєте std::vectorабоstd::array клас клас для своїх цілей.

Що стосується відредагованої частини: Незважаючи на те, що structце елемент із C, ви визначаєте конструктор та функції оператора, які роблять його C ++ class. Отже, ваш structне збирається в чистому С!


Який твій погляд? std::vectorабо std::arrayтакож не можуть містити посилання. Стандарт C ++ досить явний, що жоден контейнер не може.
підкреслюй_d
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.