Це примха / функція в C ++. Хоча ми не думаємо про посилання як про типи, вони насправді "сидять" у системі типів. Незважаючи на те, що це здається незручним (враховуючи, що при використанні посилань семантика посилань відбувається автоматично, і посилання "зникає з дороги"), є кілька захищених причин, чому посилання моделюються в системі типів, а не як окремий атрибут поза типу.
По-перше, давайте врахуємо, що не кожен атрибут оголошеного імені повинен бути в системі типів. З мови С ми маємо "клас зберігання" та "зв'язок". Ім’я може бути введено як extern const int ri
, де extern
вказує статичний клас зберігання та наявність зв’язку. Тип справедливийconst int
.
С ++, очевидно, охоплює уявлення про те, що вирази мають атрибути, що перебувають поза системою типів. Зараз мова має поняття "клас цінностей", що є спробою впорядкувати зростаючу кількість нетипових атрибутів, які може мати вираз.
Проте посилання - це типи. Чому?
Раніше в підручниках з C ++ пояснювалося, що декларація типу const int &ri
введеного ri
має тип const int
, але має посилальну семантику. Ця посилальна семантика не була типом; це був просто своєрідний атрибут, що вказує на незвичний зв’язок між назвою та місцем зберігання. Крім того, той факт, що посилання не є типами, використовувався для обґрунтування того, чому ви не можете побудувати типи на основі посилань, хоча синтаксис побудови типу це дозволяє. Наприклад, масиви або вказівники на посилання неможливі: const int &ari[5]
і const int &*pri
.
Але насправді посилання є типами і тому decltype(ri)
отримує якийсь вузол типу посилання, який є некваліфікованим. Ви повинні пройти повз цей вузол у дереві типів, щоб перейти до базового типу за допомогою remove_reference
.
Коли ви використовуєте ri
, посилання прозоро вирішується, так що ri
"виглядає і відчувається як i
" і може бути названо "псевдонімом" для нього. Однак у системі типів ri
насправді є тип, який є " посиланням на const int
".
Чому це типи посилань?
Врахуйте, що якби посилання не були типами, то ці функції вважалися б такими самими:
void foo(int);
void foo(int &);
Це просто не може бути з причин, які є досить очевидними. Якби вони мали однаковий тип, це означає, що будь-яка декларація була б придатною для будь-якого визначення, і тому кожна(int)
функція повинна була б підозрюватися у прийнятті посилання.
Подібним чином, якби посилання не були типами, то ці два оголошення класу були б еквівалентними:
class foo {
int m;
};
class foo {
int &m;
};
Було б правильно, щоб одна одиниця перекладу використовувала одну декларацію, а інша одиниця перекладу в тій самій програмі використовувала б іншу декларацію.
Справа в тому, що посилання передбачає різницю у реалізації, і неможливо відокремити це від типу, оскільки тип у C ++ пов’язаний із реалізацією сутності: його "макет" у бітах, так би мовити. Якщо дві функції мають однаковий тип, їх можна викликати з однаковими умовами двійкового виклику: ABI однаковий. Якщо дві структури або класи мають однаковий тип, їх розмітка однакова, як і семантика доступу до всіх членів. Наявність посилань змінює ці аспекти типів, тому включити їх до системи типів є простим дизайнерським рішенням. (Однак зверніть увагу на контраргумент тут: членом структури / класу може бутиstatic
, що також змінює представлення; проте це не тип!)
Таким чином, посилання в системі типів є "громадянами другого класу" (не на відміну від функцій та масивів у ISO C). Існують певні речі, які ми не можемо «зробити» із посиланнями, наприклад, оголосити вказівники на посилання або масиви з них. Але це не означає, що вони не є типажами. Вони просто не є типами таким чином, щоб це мало сенс.
Не всі ці обмеження другого класу є суттєвими. Враховуючи те, що існують структури посилань, можуть бути масиви посилань! Напр
int x = 0, y = 0;
int &ar[2] = { x, y };
Це просто не реалізовано в C ++, ось і все. Однак вказівники на посилання взагалі не мають сенсу, оскільки піднятий з посилання вказівник просто переходить на об'єкт, на який посилається. Можлива причина, чому немає масивів посилань, полягає в тому, що люди на C ++ вважають масиви своєрідною функцією низького рівня, успадкованою від C, яка багато в чому порушена, непоправно, і вони не хочуть торкатися масивів як основа для чогось нового. Однак існування масивів посилань може стати наочним прикладом того, як посилання мають бути типами.
Не-const
-qualifiable типів: знайдені в ISO C90, теж!
Деякі відповіді натякають на той факт, що посилання не беруть const
кваліфікатор. Це скоріше червоний оселедець, оскільки в декларації const int &ri = i
навіть не робиться спроба зробити const
кваліфіковане посилання: це посилання на тип, що відповідає вимогам (який сам по собі не є const
). Так само, як const in *ri
оголошує вказівник на щось const
, але сам цей вказівник не єconst
.
Тим не менш, це правда, що посилання не можуть містити const
самі кваліфікатор.
Однак це не так дивно. Навіть мовою ISO C 90 не всі типи можуть бутиconst
. А саме масивів бути не може.
По-перше, синтаксис не існує для оголошення масиву const: int a const [42]
помилковим.
Однак те, що намагається зробити наведена вище декларація, може бути виражене через проміжний продукт typedef
:
typedef int array_t[42];
const array_t a;
Але це робить не те, що виглядає так. У цій декларації кваліфікується не a
хто const
, а елементи! Тобто це a[0]
єconst int
, але a
є просто "масивом int". Отже, для цього не потрібна діагностика:
int *p = a;
Це робить:
a[0] = 1;
Знову ж таки, це підкреслює думку про те, що посилання є певним чином "другим класом" у системі типів, як масиви.
Зверніть увагу, що аналогія проводиться ще глибше, оскільки масиви також мають "невидиму поведінку перетворення", як посилання. Без програміста, який повинен використовувати будь-який явний оператор, ідентифікатор a
автоматично перетворюється на int *
покажчик, ніби вираз &a[0]
був використаний. Це аналогічно способу посиланняri
, коли ми використовуємо його як основний вираз, магічно позначає об’єктi
до якого воно прив’язане. Це просто черговий "розпад" на зразок "масиву для розпаду вказівника".
І подібно до того, як ми не повинні заплутатися від "масиву для вказівника", що перетворюється на помилково думаючи, що "масиви - це просто вказівники на C і C ++", ми також не повинні думати, що посилання - це просто псевдоніми, які не мають власного типу.
Коли decltype(ri)
пригнічується звичайне перетворення посилання на його референтний об'єкт, це не так сильно відрізняється від sizeof a
придушення перетворення масиву в покажчик та оперування типом масиву для обчислення його розміру.
boolalpha(cout)
дуже незвично. Ви можетеstd::cout << boolalpha
замість цього зробити .