Tại sao các tham chiếu không phải là "const" trong C ++?


86

Chúng tôi biết rằng một "biến const" cho biết rằng sau khi được gán, bạn không thể thay đổi biến, như sau:

int const i = 1;
i = 2;

Chương trình trên sẽ không biên dịch được; gcc nhắc với một lỗi:

assignment of read-only variable 'i'

Không sao, tôi có thể hiểu nó, nhưng ví dụ sau vượt quá sự hiểu biết của tôi:

#include<iostream>
using namespace std;
int main()
{
    boolalpha(cout);
    int const i = 1;
    cout << is_const<decltype(i)>::value << endl;
    int const &ri = i;
    cout << is_const<decltype(ri)>::value << endl;
    return 0;
}

Nó xuất ra

true
false

Kỳ dị. Chúng ta biết rằng một khi tham chiếu được liên kết với tên / biến, chúng ta không thể thay đổi ràng buộc này, chúng ta thay đổi đối tượng bị ràng buộc của nó. Vì vậy, tôi cho rằng kiểu của riphải giống như i: khi nào ilà an int const, tại sao rikhông const?


3
Ngoài ra, boolalpha(cout)là rất bất thường. Bạn có thể làm std::cout << boolalphathay thế.
isanae

6
Quá nhiều vì rilà một "bí danh" không thể phân biệt được i.
Kaz

1
Điều này là do một tham chiếu luôn bị ràng buộc cùng một đối tượng. icũng là một tài liệu tham khảo nhưng vì lý do lịch sử, bạn không khai báo nó một cách rõ ràng. Do đó, itham chiếu đề cập đến một bộ nhớ và rilà một tham chiếu đề cập đến cùng một bộ nhớ. Nhưng không có sự khác biệt về bản chất giữa iri. Vì bạn không thể thay đổi ràng buộc của một tham chiếu, nên không cần phải xác nhận nó là const. Và hãy để tôi khẳng định rằng bình luận @Kaz tốt hơn nhiều so với câu trả lời đã được xác thực (không bao giờ giải thích các tham chiếu bằng cách sử dụng con trỏ, ref là tên, ptr là một biến).
Jean-Baptiste Yunès

1
Câu hỏi tuyệt vời. Trước khi xem ví dụ này, tôi cũng mong đợi sẽ is_constquay lại truetrong trường hợp này. Theo tôi, đây là một ví dụ điển hình về lý do tại sao constvề cơ bản là ngược; thuộc tính "có thể thay đổi" (a la Rust's mut) có vẻ như nó sẽ nhất quán hơn.
Kyle Strand

1
Có lẽ nên đổi tiêu đề thành "tại sao is_const<int const &>::valuesai?" hoặc tương tự; Tôi đang đấu tranh để xem bất kỳ ý nghĩa nào cho câu hỏi ngoài việc hỏi về hành vi của các đặc điểm loại hình
MM

Câu trả lời:


52

Điều này có vẻ phản trực quan nhưng tôi nghĩ cách để hiểu điều này là nhận ra rằng, ở một số khía cạnh, các tham chiếu được coi về mặt cú pháp giống như con trỏ .

Điều này có vẻ hợp lý đối với một con trỏ :

int main()
{
    boolalpha(cout);

    int const i = 1;
    cout << is_const<decltype(i)>::value << endl;

    int const* ri = &i;
    cout << is_const<decltype(ri)>::value << endl;
}

Đầu ra:

true
false

Điều này là hợp lý vì chúng ta biết nó không phải là đối tượng con trỏ là const (nó có thể được thực hiện để trỏ đến nơi khác) mà nó là đối tượng đang được trỏ tới.

Vì vậy, chúng tôi thấy chính xác hằng số của chính con trỏ được trả về là false.

Nếu chúng ta muốn làm cho con trỏ bản thân constchúng ta phải nói:

int main()
{
    boolalpha(cout);

    int const i = 1;
    cout << is_const<decltype(i)>::value << endl;

    int const* const ri = &i;
    cout << is_const<decltype(ri)>::value << endl;
}

Đầu ra:

true
true

Và vì vậy tôi nghĩ rằng chúng ta thấy một sự tương đồng cú pháp với tham chiếu .

Tuy nhiên, các tham chiếu khác nhau về mặt ngữ nghĩa đối với các con trỏ, đặc biệt là ở một khía cạnh quan trọng, chúng tôi không được phép đặt lại một tham chiếu đến một đối tượng khác sau khi được ràng buộc.

Vì vậy, mặc dù các tham chiếu chia sẻ cùng một cú pháp như con trỏ, các quy tắc khác nhau và do đó, ngôn ngữ ngăn chúng tôi khai báo chính tham chiếuconst như thế này:

int main()
{
    boolalpha(cout);

    int const i = 1;
    cout << is_const<decltype(i)>::value << endl;

    int const& const ri = i; // COMPILE TIME ERROR!
    cout << is_const<decltype(ri)>::value << endl;
}

Tôi cho rằng chúng ta không được phép làm điều này vì nó dường như không cần thiết khi các quy tắc ngôn ngữ ngăn tham chiếu được phục hồi giống như cách một con trỏ có thể (nếu nó không được khai báo const).

Vì vậy, để trả lời câu hỏi:

Q) Tại sao “tham chiếu” không phải là “const” trong C ++?

Trong ví dụ của bạn, cú pháp làm cho sự vật được tham chiếu constgiống như cách nó sẽ xảy ra nếu bạn khai báo một con trỏ .

Dù đúng hay sai, chúng tôi không được phép để làm cho tài liệu tham khảo bản thân constnhưng nếu chúng ta nó sẽ trông như thế này:

int const& const ri = i; // not allowed

Q) chúng tôi biết một khi một tham chiếu được liên kết với một tên / biến, chúng tôi không thể thay đổi liên kết này, chúng tôi thay đổi đối tượng liên kết của nó. Vì vậy, tôi cho rằng loại riphải giống như i: khi nào ilà a int const, tại sao rikhông const?

Tại sao decltype()không chuyển giao cho các đối tượng referece là ràng buộc để?

Tôi cho rằng điều này là tương đương về ngữ nghĩa với các con trỏ và có thể chức năng của decltype()(kiểu được khai báo) là xem lại những gì đã được khai báo trước khi ràng buộc diễn ra.


13
Có vẻ như bạn đang nói "tham chiếu không thể là const vì chúng luôn là const"?
ruakh

2
Có thể đúng rằng " về mặt ngữ nghĩa tất cả các hành động đối với chúng sau khi chúng được ràng buộc đều được chuyển sang đối tượng mà chúng tham chiếu đến", nhưng OP cũng mong đợi điều đó áp dụng cho nó decltype, và nhận thấy rằng điều đó đã không.
ruakh

10
Có rất nhiều giả thiết quanh co và các phép loại suy có thể áp dụng một cách đáng ngờ đối với các con trỏ ở đây, mà tôi nghĩ rằng vấn đề sẽ gây nhầm lẫn hơn mức cần thiết, khi kiểm tra tiêu chuẩn như sonyuanyao đã làm thì đi thẳng vào điểm thực: Một tham chiếu không phải là loại cv có thể định tính nó không thể được const, do đó std::is_constphải trở lại false. Thay vào đó, họ có thể sử dụng từ ngữ có nghĩa là nó phải quay trở lại true, nhưng họ đã không làm như vậy. Đó là nó! Tất cả những thứ này về con trỏ, "Tôi giả sử", "Tôi cho là", v.v. không cung cấp bất kỳ lời giải thích thực sự nào.
underscore_d

1
@underscore_d Đây có lẽ là một câu hỏi hay hơn cho phần trò chuyện hơn là cho phần nhận xét ở đây, nhưng bạn có ví dụ về "sự nhầm lẫn không cần thiết" từ cách nghĩ này về tài liệu tham khảo không? Nếu chúng có thể được thực hiện theo cách đó, thì có vấn đề gì khi nghĩ chúng theo cách đó? (Tôi không nghĩ rằng câu hỏi này được tính bởi vì decltypekhông phải là một hoạt động thời gian chạy, do đó, "trong thời gian chạy, tài liệu tham khảo hành xử giống như con trỏ" ý tưởng, cho dù đúng hay không, không thực sự áp dụng.
Kyle Strand

1
Tham chiếu cũng không được coi về mặt cú pháp như con trỏ. Chúng có các cú pháp khác nhau để khai báo và sử dụng. Vì vậy, tôi thậm chí không chắc bạn muốn nói gì.
Jordan Melo

55

tại sao "ri" không phải là "const"?

std::is_const kiểm tra xem loại có đủ tiêu chuẩn const hay không.

Nếu T là kiểu đủ điều kiện const (nghĩa là const, hoặc const dễ bay hơi), cung cấp giá trị hằng số thành viên bằng true. Đối với bất kỳ loại nào khác, giá trị là sai.

Nhưng tham chiếu không thể đủ tiêu chuẩn const. Tài liệu tham khảo [dcl.ref] / 1

Các tham chiếu đủ điều kiện cv không được định hình ngoại trừ khi các vòng loại cv được đưa vào thông qua việc sử dụng typedef-name ([dcl.typedef], [temp.param]) hoặc khai báo-specifier ([dcl.type.simple]) , trong trường hợp đó, các vòng loại cv bị bỏ qua.

Vì vậy, is_const<decltype(ri)>::valuesẽ trả về falsebecuase ri(tham chiếu) không phải là một loại đủ điều kiện const. Như bạn đã nói, chúng tôi không thể liên kết lại một tham chiếu sau khi khởi tạo, điều này ngụ ý rằng tham chiếu luôn là "const", mặt khác, tham chiếu đủ điều kiện const hoặc tham chiếu không đủ tiêu chuẩn const có thể không thực sự có ý nghĩa.


5
Đi thẳng vào Tiêu chuẩn và do đó đi thẳng vào vấn đề, thay vì tất cả những giả định lung tung về câu trả lời được chấp nhận.
underscore_d

5
@underscore_d Đây là một câu trả lời phù hợp cho đến khi bạn nhận ra rằng op cũng đang hỏi tại sao. "Bởi vì nó nói như vậy" không thực sự hữu ích cho khía cạnh đó của câu hỏi.
simpleuser

5
@ user9999999 Câu trả lời khác cũng không thực sự trả lời khía cạnh đó. Nếu một tham chiếu không thể được phục hồi, tại sao không is_consttrả về true? Câu trả lời đó cố gắng tạo ra một sự tương tự về cách các con trỏ có thể được lặp lại theo tùy chọn, trong khi các tham chiếu thì không - và khi làm như vậy, dẫn đến tự mâu thuẫn vì lý do tương tự. Tôi không chắc có một lời giải thích thực sự theo cách nào đó, ngoài một quyết định hơi độc đoán của những người viết Tiêu chuẩn, và đôi khi đó là điều tốt nhất chúng ta có thể hy vọng. Do đó câu trả lời này.
underscore_d

2
Một khía cạnh quan trọng, tôi nghĩ rằng, đó là decltypekhông phải là một chức năng và do đó làm việc trực tiếp trên tham chiếu riêng của mình chứ không phải là trên gọi đến đối tượng. (Điều này có lẽ phù hợp hơn với các "tài liệu tham khảo về cơ bản con trỏ" câu trả lời, nhưng tôi vẫn nghĩ rằng đó là một phần của những gì làm cho ví dụ này khó hiểu và do đó đáng nói ở đây.)
Kyle Strand

3
Để làm sáng tỏ thêm nhận xét từ @KyleStrand: decltype(name)hành động khác với một vị tướng decltype(expr). Vì vậy, ví dụ, decltype(i)là kiểu được khai báo iconst int, trong khi decltype((i))sẽ là int const &.
Daniel Schepler

18

Bạn cần sử dụng std::remove_referenceđể nhận được giá trị mà bạn đang tìm kiếm.

std::cout << std::is_const<std::remove_reference<decltype(ri)>::type>::value << std::endl;

Để biết thêm thông tin, hãy xem bài đăng này .


7

Tại sao không có macro const? Chức năng? Chữ viết? Tên các loại?

const mọi thứ chỉ là một tập hợp con của những thứ bất biến.

Vì các kiểu tham chiếu chỉ là - các kiểu - nên có thể có lý khi yêu cầu consttất cả -qualifier trên chúng để đối xứng với các kiểu khác (đặc biệt là với các kiểu con trỏ), nhưng điều này sẽ rất tẻ nhạt nhanh chóng.

Nếu C ++ có các đối tượng bất biến theo mặc định, yêu cầu mutabletừ khóa trên bất kỳ thứ gì bạn không muốn const, thì điều này sẽ dễ dàng: đơn giản là không cho phép lập trình viên thêm mutablevào các kiểu tham chiếu.

Vì nó là, chúng là bất biến mà không có trình độ.

Và, vì chúng không constđủ tiêu chuẩn, nên có lẽ sẽ khó hiểu hơn nếu is_constloại tham chiếu mang lại giá trị true.

Tôi thấy đây là một sự thỏa hiệp hợp lý, đặc biệt là vì tính bất biến vẫn được thực thi bởi thực tế là không tồn tại cú pháp nào để thay đổi một tham chiếu.


5

Đâ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 externchỉ 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 &rigiới thiệu rilà 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]const int &*pri.

Nhưng trên thực tế, các tham chiếu 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, ritrong 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ể staticthay đổ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ụ

// fantasy syntax
int x = 0, y = 0;
int &ar[2] = { x, y };

// ar[0] is now an alias for x: could be useful!

Đ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 constloạ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ó consttiê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 = ithậm chí không cố gắng tạo ra một consttham 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 *rikhai 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à acó đượ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 achỉ là "mảng của int". Do đó, điều này không yêu cầu chẩn đoán:

int *p = a; /* surprise! */

Đ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 asẽ 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 imà 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 aviệ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ó.


Bạn có rất nhiều thông tin thú vị và hữu ích ở đây (+1), nhưng nó ít nhiều bị giới hạn ở thực tế là "tài liệu tham khảo nằm trong hệ thống loại" - điều này không hoàn toàn trả lời câu hỏi của OP. Giữa câu trả lời này và câu trả lời của @ songyuanyao, tôi muốn nói rằng có quá đủ thông tin để hiểu tình huống, nhưng nó giống như nền tảng cần thiết cho một câu trả lời hơn là một câu trả lời hoàn chỉnh.
Kyle Strand

1
Ngoài ra, tôi nghĩ rằng câu này đáng làm nổi bật: "Khi bạn sử dụng ri, tham chiếu được giải quyết một cách minh bạch ..." Một điểm chính (mà tôi đã đề cập trong các nhận xét nhưng cho đến nay vẫn chưa xuất hiện trong bất kỳ câu trả lời nào ) là decltype không thực hiện độ phân giải trong suốt này (nó không phải là một chức năng, vì vậy rikhông được "sử dụng" theo nghĩa bạn mô tả). Phù hợp này trong rất độc đáo với toàn bộ tập trung của bạn trên hệ thống loại - họ kết nối chính là decltypemột hoạt động kiểu hệ thống .
Kyle Strand

Rất tiếc, nội dung về mảng có vẻ giống như một tiếp tuyến, nhưng câu cuối cùng rất hữu ích.
Kyle Strand

..... mặc dù vào thời điểm này tôi đã bỏ phiếu tán bạn và tôi không phải là OP, vì vậy bạn không thực sự đạt được hoặc mất bất cứ điều gì bằng cách lắng nghe lời phê bình của tôi ....
Kyle Strand

Rõ ràng là một câu trả lời dễ dàng (và đúng) chỉ cho chúng ta tiêu chuẩn nhưng tôi thích tư duy nền tảng ở đây mặc dù một số người có thể tranh luận rằng một số phần của nó là giả định. +1.
Steve Kidd

-5

const X & x ”có nghĩa là x bí danh của một đối tượng X, nhưng bạn không thể thay đổi đối tượng X đó thông qua x.

Và xem std :: is_const .


điều này thậm chí không cố gắng trả lời câu hỏi.
bolov
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.