Đây là một tính năng đặc biệt trong C ++. Mặc dù chúng ta không coi các tham chiếu là các kiểu, nhưng trên thực tế, chúng "ngồi" trong hệ thống kiểu. Mặc dù điều này có vẻ khó xử (vì khi tham chiếu được sử dụng, ngữ nghĩa của tham chiếu sẽ tự động xảy ra và tham chiếu "không hoạt động"), có một số lý do có thể bảo vệ được tại sao tham chiếu được mô hình hóa trong hệ thống kiểu thay vì dưới dạng một thuộc tính riêng biệt bên ngoài kiểu.
Đầu tiên, chúng ta hãy xem xét rằng không phải mọi thuộc tính của tên đã khai báo đều phải nằm trong hệ thống kiểu. Từ ngôn ngữ C, chúng ta có "lớp lưu trữ" và "liên kết". Một tên có thể được giới thiệu là extern const int ri
, nơi extern
chỉ ra lớp lưu trữ tĩnh và sự hiện diện của liên kết. Loại chỉ là const int
.
C ++ rõ ràng chấp nhận quan điểm rằng các biểu thức có các thuộc tính nằm ngoài hệ thống kiểu. Ngôn ngữ hiện có một khái niệm về "lớp giá trị" là một nỗ lực để tổ chức số lượng ngày càng tăng các thuộc tính không phải kiểu mà một biểu thức có thể thể hiện.
Tuy nhiên, tham chiếu là các loại. Tại sao?
Nó từng được giải thích trong các hướng dẫn về C ++ rằng một khai báo như được const int &ri
giới thiệu ri
là có kiểu const int
, nhưng ngữ nghĩa tham chiếu. Ngữ nghĩa tham chiếu đó không phải là một kiểu; nó chỉ đơn giản là một loại thuộc tính chỉ ra mối quan hệ bất thường giữa tên và vị trí lưu trữ. Hơn nữa, thực tế là các tham chiếu không phải là các kiểu được sử dụng để hợp lý hóa lý do tại sao bạn không thể tạo các kiểu dựa trên các tham chiếu, mặc dù cú pháp xây dựng kiểu cho phép điều đó. Ví dụ: không thể sử dụng mảng hoặc con trỏ tới tham chiếu: const int &ari[5]
và const int &*pri
.
Nhưng trên thực tế, các tham chiếu là các kiểu và do đó, decltype(ri)
truy xuất một số nút kiểu tham chiếu không đủ tiêu chuẩn. Bạn phải đi xuống nút này trong cây kiểu để đến kiểu cơ bản với remove_reference
.
Khi bạn sử dụng ri
, tham chiếu được giải quyết một cách minh bạch, để ri
"trông giống như i
" và có thể được gọi là "bí danh" cho nó. Tuy nhiên, ri
trong hệ thống kiểu thực tế có một kiểu là " tham chiếu tới const int
".
Tại sao lại là các loại tham chiếu?
Hãy xem xét rằng nếu các tham chiếu không phải là kiểu, thì những hàm này sẽ được coi là có cùng kiểu:
void foo(int);
void foo(int &);
Điều đó chỉ đơn giản là không thể vì những lý do khá rõ ràng. Nếu chúng có cùng kiểu, điều đó có nghĩa là một trong hai khai báo sẽ phù hợp với một trong hai định nghĩa và vì vậy mọi (int)
hàm sẽ phải bị nghi ngờ là tham chiếu.
Tương tự, nếu các tham chiếu không phải là kiểu, thì hai khai báo lớp này sẽ tương đương:
class foo {
int m;
};
class foo {
int &m;
};
Sẽ đúng khi một đơn vị dịch sử dụng một khai báo và đơn vị dịch khác trong cùng một chương trình sử dụng khai báo khác.
Thực tế là một tham chiếu ngụ ý sự khác biệt trong việc triển khai và không thể tách nó ra khỏi kiểu, bởi vì kiểu trong C ++ liên quan đến việc triển khai một thực thể: "bố cục" của nó theo từng bit. Nếu hai hàm có cùng kiểu, chúng có thể được gọi với cùng một quy ước gọi nhị phân: ABI giống nhau. Nếu hai cấu trúc hoặc lớp có cùng kiểu, thì bố cục của chúng cũng như ngữ nghĩa của quyền truy cập vào tất cả các thành viên giống nhau. Sự hiện diện của các tham chiếu thay đổi các khía cạnh này của các kiểu, và do đó, việc kết hợp chúng vào hệ thống kiểu là một quyết định thiết kế đơn giản. (Tuy nhiên, hãy lưu ý đối số phân cấp ở đây: một cấu trúc / thành viên lớp cũng có thể static
thay đổi đại diện; nhưng đó không phải là kiểu!)
Do đó, các tham chiếu nằm trong hệ thống kiểu là "công dân hạng hai" (không giống như các hàm và mảng trong ISO C). Có một số việc chúng ta không thể "làm" với các tham chiếu, chẳng hạn như khai báo các con trỏ tới các tham chiếu hoặc các mảng của chúng. Nhưng điều đó không có nghĩa là chúng không phải là loại. Chúng không phải là loại theo cách mà nó có ý nghĩa.
Không phải tất cả những hạn chế hạng hai này đều cần thiết. Cho rằng có cấu trúc tham chiếu, có thể có mảng tham chiếu! Ví dụ
int x = 0, y = 0;
int &ar[2] = { x, y };
Điều này chỉ không được triển khai trong C ++, vậy thôi. Mặc dù vậy, con trỏ đến tham chiếu không có ý nghĩa gì cả, bởi vì một con trỏ được nâng lên từ một tham chiếu chỉ đi đến đối tượng được tham chiếu. Lý do có thể là tại sao không có mảng tham chiếu là người C ++ coi mảng là một loại tính năng cấp thấp được kế thừa từ C bị hỏng theo nhiều cách không thể sửa chữa và họ không muốn chạm vào mảng như cơ sở cho bất cứ điều gì mới. Tuy nhiên, sự tồn tại của các mảng tham chiếu sẽ làm cho một ví dụ rõ ràng về cách các tham chiếu phải là các kiểu.
Không const
loại -qualifiable: tìm thấy trong ISO C90, quá!
Một số câu trả lời đang ám chỉ thực tế rằng các tham chiếu không có const
tiêu chuẩn định tính. Đó đúng hơn là một con cá trích đỏ, bởi vì khai báo const int &ri = i
thậm chí không cố gắng tạo ra một const
tham chiếu đủ điều kiện: nó là một tham chiếu đến một loại đủ điều kiện const (bản thân nó không phải const
). Giống như const in *ri
khai báo một con trỏ đến một cái gì đó const
, nhưng con trỏ đó không phải là chính nó const
.
Điều đó nói rằng, đúng là các tham chiếu không thể tự mang định tính const
.
Tuy nhiên, điều này không phải là quá kỳ lạ. Ngay cả trong ngôn ngữ ISO C 90, không phải tất cả các loại đều có thể được const
. Cụ thể, mảng không thể được.
Thứ nhất, cú pháp không tồn tại để khai báo một mảng const: int a const [42]
là sai.
Tuy nhiên, những gì khai báo trên đang cố gắng thực hiện có thể được thể hiện thông qua một trung gian typedef
:
typedef int array_t[42];
const array_t a;
Nhưng điều này không làm những gì nó trông giống như nó. Trong tuyên bố này, nó không phải là a
có được const
đủ điều kiện, nhưng các yếu tố! Có nghĩa là, a[0]
là một const int
, nhưng a
chỉ là "mảng của int". Do đó, điều này không yêu cầu chẩn đoán:
int *p = a;
Điều này làm:
a[0] = 1;
Một lần nữa, điều này nhấn mạnh ý tưởng rằng các tham chiếu theo một nghĩa nào đó là "lớp thứ hai" trong hệ thống kiểu, như mảng.
Lưu ý cách loại suy diễn ra sâu sắc hơn, vì mảng cũng có "hành vi chuyển đổi vô hình", giống như tham chiếu. Không cần lập trình viên phải sử dụng bất kỳ toán tử rõ ràng nào, mã định danh a
sẽ tự động biến thành một int *
con trỏ, như thể biểu thức &a[0]
đã được sử dụng. Điều này tương tự như cách một tham chiếu ri
, khi chúng ta sử dụng nó làm biểu thức chính, biểu thị một cách kỳ diệu đối tượng i
mà nó bị ràng buộc. Nó chỉ là một "phân rã" khác như "phân rã mảng thành con trỏ".
Và cũng giống như chúng ta không được bối rối bởi "mảng thành con trỏ" phân rã thành suy nghĩ sai lầm rằng "mảng chỉ là con trỏ trong C và C ++", chúng ta cũng không được nghĩ rằng các tham chiếu chỉ là bí danh không có kiểu riêng của chúng.
Khi decltype(ri)
ngăn chặn chuyển đổi thông thường của tham chiếu sang đối tượng tham chiếu của nó, điều này không khác quá nhiều so với sizeof a
việc ngăn chuyển đổi mảng thành con trỏ và hoạt động trên chính kiểu mảng để tính toán kích thước của nó.
boolalpha(cout)
là rất bất thường. Bạn có thể làmstd::cout << boolalpha
thay thế.