IT-Swarm.Net

Giá trị, giá trị, xvalues, glvalues ​​và prvalues ​​là gì?

Trong C++ 03, một biểu thức là rvalue hoặc lvalue.

Trong C++ 11, một biểu thức có thể là:

  1. giá trị
  2. giá trị
  3. xvalue
  4. glvalue
  5. giá trị

Hai loại đã trở thành năm loại.

  • Những loại biểu thức mới này là gì?
  • Làm thế nào để các danh mục mới này liên quan đến các loại giá trị hiện tại và giá trị hiện tại?
  • Các danh mục rvalue và lvalue trong C++ 0x có giống như trong C++ 03 không?
  • Tại sao những danh mục mới cần thiết? Có phải WG21 các vị thần chỉ đang cố làm chúng ta bối rối không?
1244
James McNellis

Tôi đoán tài liệu này có thể đóng vai trò giới thiệu không quá ngắn: n3055

Toàn bộ vụ thảm sát bắt đầu với ngữ nghĩa di chuyển. Khi chúng ta có các biểu thức có thể được di chuyển và không được sao chép, đột nhiên dễ dàng nắm bắt các quy tắc yêu cầu phân biệt giữa các biểu thức có thể được di chuyển và theo hướng nào.

Từ những gì tôi đoán dựa trên dự thảo, sự phân biệt giá trị r/l vẫn giữ nguyên, chỉ trong bối cảnh mọi thứ chuyển động trở nên lộn xộn.

Họ có cần không? Có lẽ là không nếu chúng ta muốn mất các tính năng mới. Nhưng để cho phép tối ưu hóa tốt hơn có lẽ chúng ta nên nắm lấy chúng.

Trích dẫn n3055 :

  • Một lvalue (được gọi là lịch sử, bởi vì giá trị có thể xuất hiện ở phía bên trái của biểu thức gán) chỉ định một hàm hoặc một đối tượng. [Ví dụ: Nếu E là một biểu thức của loại con trỏ, thì *E là một biểu thức giá trị tham chiếu đến đối tượng hoặc hàm mà E trỏ tới. Một ví dụ khác, kết quả của việc gọi một hàm có kiểu trả về là tham chiếu giá trị là một giá trị.]
  • Một xvalue (một giá trị của EXpires ') cũng đề cập đến một đối tượng, thường là gần cuối vòng đời của nó (để tài nguyên của nó có thể được di chuyển, cho thí dụ). Một xvalue là kết quả của một số loại biểu thức liên quan đến các tham chiếu rvalue. [Ví dụ: Kết quả của việc gọi hàm có kiểu trả về là tham chiếu giá trị là xvalue.]
  • A glvalue (Tổng quát hóa lvalue) là một lvalue hoặc xvalue .
  • Một rvalue (được gọi là theo lịch sử, bởi vì các giá trị có thể xuất hiện ở phía bên phải của biểu thức gán) là một giá trị tạm thời, một đối tượng tạm thời hoặc subobject của chúng, hoặc một giá trị không được liên kết với một đối tượng.
  • A prvalue (“pure” rvalue) is an rvalue that is not an xvalue. [Example: The result of calling a function whose return type is not a reference is a prvalue]

Tài liệu được đề cập là một tài liệu tham khảo tuyệt vời cho câu hỏi này, bởi vì nó cho thấy những thay đổi chính xác trong tiêu chuẩn đã xảy ra do sự ra đời của danh pháp mới.

584
Kornel Kisielewicz

Những loại biểu thức mới này là gì?

FCD (n3092) có một mô tả xuất sắc:

- Một giá trị (được gọi là, theo lịch sử, bởi vì giá trị có thể xuất hiện ở phía bên trái của biểu thức gán) chỉ định một chức năng hoặc một đối tượng. [Ví dụ: Nếu E là biểu thức của loại con trỏ, thì * E là biểu thức giá trị tham chiếu đến đối tượng hoặc hàm mà E trỏ tới. Một ví dụ khác, kết quả của việc gọi một hàm có kiểu trả về là tham chiếu giá trị là một giá trị. Ví dụ

- Một giá trị xvalue (một giá trị của eXpires ') cũng đề cập đến một đối tượng, thường là gần cuối vòng đời của nó (ví dụ như tài nguyên của nó có thể được di chuyển). Một xvalue là kết quả của một số loại biểu thức liên quan đến các tham chiếu rvalue (8.3.2). [Ví dụ: Kết quả của việc gọi một hàm có kiểu trả về là tham chiếu rvalue là xvalue. Ví dụ

- Một giá trị (giá trị chung của Lvalue) là một giá trị hoặc một giá trị xvalue.

- Một giá trị (được gọi là, theo lịch sử, bởi vì các giá trị có thể xuất hiện ở phía bên phải của biểu thức gán) là một giá trị, một đối tượng tạm thời (12.2) hoặc phụ đề của nó hoặc một giá trị không được liên kết với một đối tượng.

- Một giá trị (giá trị thuần túy rvalue) là một giá trị không phải là giá trị xvalue. [Ví dụ: Kết quả của việc gọi một hàm có kiểu trả về không phải là tham chiếu là một giá trị. Giá trị của một nghĩa đen như 12, 7.3e5 hoặc true cũng là một giá trị. Ví dụ

Mỗi biểu thức thuộc về chính xác một trong các phân loại cơ bản trong phân loại này: lvalue, xvalue hoặc prvalue. Thuộc tính này của một biểu thức được gọi là thể loại giá trị của nó. [Lưu ý: Cuộc thảo luận của từng toán tử tích hợp trong Khoản 5 chỉ ra loại giá trị mà nó mang lại và các loại giá trị của toán hạng mà nó mong đợi. Ví dụ, các toán tử gán gán dựng sẵn mong đợi toán hạng bên trái là một giá trị và toán hạng bên phải là một giá trị và kết quả là một giá trị. Toán tử do người dùng định nghĩa là các hàm và các loại giá trị mà chúng mong đợi và năng suất được xác định bởi các loại tham số và trả về của chúng. Lưu ý

Tôi đề nghị bạn đọc toàn bộ phần 3.10 Giá trị và giá trị mặc dù.

Làm thế nào để các danh mục mới này liên quan đến các loại giá trị hiện tại và giá trị hiện tại?

Lần nữa:

Taxonomy

Các danh mục rvalue và lvalue trong C++ 0x có giống như trong C++ 03 không?

Các ngữ nghĩa của các giá trị đã phát triển đặc biệt với sự ra đời của ngữ nghĩa di chuyển.

Tại sao những danh mục mới cần thiết?

Vì vậy, di chuyển xây dựng/phân công có thể được xác định và hỗ trợ.

325
dirkgently

Tôi sẽ bắt đầu với câu hỏi cuối cùng của bạn:

Tại sao những danh mục mới cần thiết?

Tiêu chuẩn C++ chứa nhiều quy tắc liên quan đến loại giá trị của biểu thức. Một số quy tắc làm nên sự khác biệt giữa lvalue và rvalue. Ví dụ, khi nói đến độ phân giải quá tải. Các quy tắc khác làm nên sự khác biệt giữa glvalue và prvalue. Ví dụ: bạn có thể có một giá trị với loại không hoàn chỉnh hoặc trừu tượng nhưng không có giá trị với loại không hoàn chỉnh hoặc trừu tượng. Trước khi chúng tôi có thuật ngữ này, các quy tắc thực sự cần phân biệt giữa glvalue/prvalue được gọi là lvalue/rvalue và chúng vô tình sai hoặc chứa rất nhiều giải thích và ngoại lệ cho quy tắc a la "... trừ khi giá trị là do không được đặt tên tham chiếu giá trị ... ". Vì vậy, có vẻ như là một ý tưởng tốt để chỉ đưa ra các khái niệm về giá trị và giá trị tên riêng của họ.

Những loại biểu thức mới này là gì? Làm thế nào để các danh mục mới này liên quan đến các loại giá trị hiện tại và giá trị hiện tại?

Chúng tôi vẫn có các thuật ngữ lvalue và rvalue tương thích với C++ 98. Chúng tôi chỉ chia giá trị thành hai nhóm nhỏ, xvalues ​​và prvalues, và chúng tôi gọi giá trị và xvalues ​​là giá trị. Xvalues ​​là một loại danh mục giá trị mới cho các tham chiếu giá trị không tên. Mỗi biểu thức là một trong ba biểu thức sau: lvalue, xvalue, prvalue. Một sơ đồ Venn sẽ trông như thế này:

    ______ ______
   /      X      \
  /      / \      \
 |   l  | x |  pr  |
  \      \ /      /
   \______X______/
       gl    r

Ví dụ với các chức năng:

int   prvalue();
int&  lvalue();
int&& xvalue();

Nhưng cũng đừng quên rằng các tham chiếu rvalue có tên là giá trị:

void foo(int&& t) {
  // t is initialized with an rvalue expression
  // but is actually an lvalue expression itself
}
172
sellibitze

Tại sao những danh mục mới cần thiết? Có phải các vị thần WG21 chỉ đang cố gắng làm chúng ta bối rối không?

Tôi không cảm thấy rằng các câu trả lời khác (tốt mặc dù nhiều trong số chúng là) thực sự nắm bắt được câu trả lời cho câu hỏi đặc biệt này. Vâng, các loại này và như vậy tồn tại để cho phép di chuyển ngữ nghĩa, nhưng sự phức tạp tồn tại vì một lý do. Đây là quy tắc bất khả xâm phạm về công cụ di chuyển trong C++ 11:

Bạn sẽ chỉ di chuyển khi chắc chắn an toàn để làm như vậy.

Đó là lý do tại sao các danh mục này tồn tại: để có thể nói về các giá trị khi di chuyển khỏi chúng an toàn và nói về các giá trị không tồn tại.

Trong phiên bản đầu tiên của các tham chiếu giá trị r, chuyển động đã xảy ra dễ dàng. Quá dễ dàng. Dễ dàng đủ rằng có rất nhiều tiềm năng để di chuyển mọi thứ khi người dùng không thực sự có ý nghĩa.

Dưới đây là các trường hợp an toàn để di chuyển một cái gì đó:

  1. Khi đó là tạm thời hoặc phụ đề của chúng. (giá trị)
  2. Khi người dùng có nói rõ ràng để di chuyển nó .

Nếu bạn làm điều này:

SomeType &&Func() { ... }

SomeType &&val = Func();
SomeType otherVal{val};

Cái này làm gì Trong các phiên bản cũ hơn của thông số kỹ thuật, trước khi 5 giá trị xuất hiện, điều này sẽ gây ra một động thái. Tất nhiên nó. Bạn đã chuyển một tham chiếu giá trị cho hàm tạo và do đó, nó liên kết với hàm tạo có tham chiếu giá trị. Đó là hiển nhiên.

Chỉ có một vấn đề với điều này; bạn đã không yêu cầu di chuyển nó. Ồ, bạn có thể nói rằng && nên là một đầu mối, nhưng điều đó không thay đổi thực tế rằng nó đã phá vỡ quy tắc. val không phải là tạm thời vì tạm thời không có tên. Bạn có thể đã kéo dài thời gian tồn tại tạm thời, nhưng điều đó có nghĩa là nó không tạm thời ; nó giống như bất kỳ biến stack nào khác.

Nếu đó không phải là tạm thời và bạn không yêu cầu di chuyển nó, thì việc di chuyển là sai.

Giải pháp rõ ràng là biến val thành một giá trị. Điều này có nghĩa là bạn không thể di chuyển từ nó. Được rồi, tốt thôi; Nó được đặt tên, vì vậy nó là một giá trị.

Khi bạn làm điều đó, bạn không còn có thể nói rằng SomeType&& có nghĩa là điều tương tự ở mọi nơi. Bây giờ bạn đã phân biệt giữa các tham chiếu rvalue có tên và các tham chiếu rvalue chưa được đặt tên. Vâng, tham chiếu rvalue được đặt tên là giá trị; đó là giải pháp của chúng tôi ở trên. Vậy chúng ta gọi những tham chiếu rvalue không tên là gì (giá trị trả về từ Func ở trên)?

Đó không phải là một giá trị, bởi vì bạn không thể di chuyển từ một giá trị. Và chúng tôi cần để có thể di chuyển bằng cách trả về &&; Làm thế nào khác bạn có thể nói rõ ràng để di chuyển một cái gì đó? Đó là những gì std::move trả về, sau tất cả. Đó không phải là một giá trị (kiểu cũ), bởi vì nó có thể nằm ở bên trái của một phương trình (mọi thứ thực sự phức tạp hơn một chút, xem câu hỏi này và các bình luận bên dưới). Nó không phải là một giá trị cũng không phải là một giá trị; đó là một loại điều mới.

Những gì chúng tôi có là một giá trị mà bạn có thể coi là một giá trị, ngoại trừ mà nó có thể di chuyển hoàn toàn từ đó. Chúng tôi gọi nó là một giá trị x.

Lưu ý rằng xvalues ​​là những gì làm cho chúng tôi đạt được hai loại giá trị khác:

  • Một giá trị thực sự chỉ là tên mới cho loại giá trị trước đó, tức là chúng là các giá trị mà không xvalues.

  • Glvalues ​​là sự kết hợp của xvalues ​​và lvalues ​​trong một nhóm, bởi vì chúng có chung rất nhiều tính chất.

Vì vậy, thực sự, tất cả bắt nguồn từ xvalues ​​và sự cần thiết phải hạn chế di chuyển đến những nơi chính xác và duy nhất. Những nơi được xác định bởi thể loại giá trị; giá trị là các bước di chuyển ngầm và xvalues ​​là các bước di chuyển rõ ràng (std::move trả về một giá trị x).

151
Nicol Bolas

IMHO, lời giải thích tốt nhất về ý nghĩa của nó đã cho chúng tôi Stroustrup + đưa vào các ví dụ về Dániel SándorMohan :

Stroustrup:

Bây giờ tôi đã rất lo lắng. Rõ ràng chúng tôi đã hướng đến một bế tắc hoặc một mớ hỗn độn hoặc cả hai. Tôi đã dành thời gian ăn trưa để phân tích để xem thuộc tính nào (của các giá trị) là độc lập. Chỉ có hai thuộc tính độc lập:

  • has identity - tức là và địa chỉ, một con trỏ, người dùng có thể xác định xem hai bản sao có giống nhau không, v.v.
  • can be moved from - tức là chúng tôi được phép rời khỏi nguồn của một "bản sao" trong một số trạng thái không xác định, nhưng hợp lệ

Điều này dẫn tôi đến kết luận rằng có chính xác ba loại giá trị (sử dụng thủ thuật ký hiệu regex sử dụng chữ in hoa để biểu thị phủ định - Tôi đã vội vàng):

  • iM: có danh tính và không thể được chuyển từ
  • im: có danh tính và có thể được chuyển từ (ví dụ: kết quả của việc truyền một giá trị sang tham chiếu giá trị)
  • Im: không có danh tính và có thể được chuyển từ.

    Khả năng thứ tư, IM, (không có danh tính và không thể di chuyển) không hữu ích trong C++ (hoặc, tôi nghĩ) trong bất kỳ ngôn ngữ nào khác.

Ngoài ba phân loại cơ bản của các giá trị, chúng tôi có hai khái quát rõ ràng tương ứng với hai thuộc tính độc lập:

  • i: có danh tính
  • m: có thể được chuyển từ

Điều này khiến tôi phải đặt sơ đồ này lên bảng: enter image description here

Đặt tên

Tôi quan sát thấy rằng chúng tôi chỉ có quyền tự do giới hạn tên: Hai điểm ở bên trái (được gắn nhãn iMi) là những gì mà những người có ít nhiều hình thức đã gọi là lvalues và hai điểm ở bên phải (được gắn nhãn mIm) là những gì mà những người có ít nhiều hình thức đã gọi là rvalues. Điều này phải được phản ánh trong cách đặt tên của chúng tôi. Đó là, "chân" bên trái của W nên có các tên liên quan đến lvalue và "chân" bên phải của W nên có các tên liên quan đến rvalue. Tôi lưu ý rằng toàn bộ cuộc thảo luận này/vấn đề phát sinh từ việc giới thiệu các tài liệu tham khảo giá trị và di chuyển ngữ nghĩa. Những khái niệm này chỉ đơn giản là không tồn tại trong thế giới Strachey, chỉ bao gồm rvalueslvalues. Có người quan sát rằng những ý tưởng đó

  • Mỗi valuelvalue hoặc là rvalue
  • lvalue không phải là rvaluervalue không phải là lvalue

được nhúng sâu trong ý thức của chúng tôi, các thuộc tính rất hữu ích và dấu vết của sự phân đôi này có thể được tìm thấy trên tất cả các tiêu chuẩn dự thảo. Tất cả chúng ta đều đồng ý rằng chúng ta nên bảo tồn các tính chất đó (và làm cho chúng chính xác). Điều này tiếp tục hạn chế lựa chọn đặt tên của chúng tôi. Tôi đã quan sát rằng từ ngữ của thư viện chuẩn sử dụng rvalue có nghĩa là m (khái quát hóa), để duy trì kỳ vọng và văn bản của thư viện chuẩn là điểm dưới cùng bên phải của W nên được đặt tên rvalue.

Điều này dẫn đến một cuộc thảo luận tập trung về đặt tên. Trước tiên, chúng tôi cần quyết định về lvalue. Nên lvalue có nghĩa là iM hoặc khái quát hóa i? Được dẫn dắt bởi Doug Gregor, chúng tôi đã liệt kê các địa điểm trong từ ngữ ngôn ngữ cốt lõi trong đó Word lvalue đủ điều kiện có nghĩa là cái này hay cái kia. Một danh sách đã được lập và trong hầu hết các trường hợp và trong văn bản khó nhất/dễ vỡ nhất lvalue hiện có nghĩa là iM. Đây là ý nghĩa cổ điển của lvalue vì "ngày xưa" không có gì được di chuyển; move là một khái niệm mới trong C++0x. Ngoài ra, việc đặt tên điểm mở rộng của Wlvalue cung cấp cho chúng ta thuộc tính mà mọi giá trị là lvalue hoặc một rvalue, nhưng không phải cả hai.

Vì vậy, điểm trên cùng bên trái của Wlvalue và điểm dưới cùng bên phải là rvalue. Điều gì làm cho điểm dưới cùng bên trái và trên cùng bên phải? Điểm dưới cùng bên trái là một khái quát của giá trị cổ điển, cho phép di chuyển. Vì vậy, nó là một generalized lvalue. Chúng tôi đặt tên cho nó là glvalue. Bạn có thể ngụy biện về chữ viết tắt, nhưng (tôi nghĩ) không phải với logic. Chúng tôi đã giả định rằng trong việc sử dụng nghiêm túc generalized lvalue bằng cách nào đó sẽ được viết tắt bằng mọi cách, vì vậy chúng tôi nên làm điều đó ngay lập tức (hoặc có nguy cơ nhầm lẫn). Điểm trên cùng bên phải của W ít tổng quát hơn phía dưới bên phải (bây giờ, như mọi khi, được gọi là rvalue). Điểm đó thể hiện khái niệm thuần túy ban đầu của một đối tượng mà bạn có thể di chuyển từ đó bởi vì nó không thể được nhắc đến lần nữa (ngoại trừ bởi một hàm hủy). Tôi thích cụm từ specialized rvalue trái ngược với generalized lvalue nhưng pure rvalue viết tắt là prvalue đã thắng (và có lẽ đúng như vậy). Vì vậy, chân trái của W là lvalueglvalue và chân phải là prvaluervalue. Ngẫu nhiên, mọi giá trị đều là giá trị hoặc không phải là giá trị.

Điều này rời khỏi giữa trên cùng của W: im; đó là, các giá trị có bản sắc và có thể được di chuyển. Chúng tôi thực sự không có gì hướng dẫn chúng tôi một cái tên hay cho những con thú bí truyền đó. Chúng rất quan trọng đối với những người làm việc với văn bản tiêu chuẩn (dự thảo), nhưng không có khả năng trở thành một tên hộ gia đình. Chúng tôi đã không tìm thấy bất kỳ ràng buộc thực sự nào trong việc đặt tên để hướng dẫn chúng tôi, vì vậy chúng tôi đã chọn ‘x, cho trung tâm, không xác định, lạ, chỉ xpert hoặc thậm chí x-xếp hạng.

Steve showing off the final product

123
Ivan Kush

GIỚI THIỆU

ISOC++ 11 (chính thức ISO/IEC 14882: 2011) là phiên bản gần đây nhất của tiêu chuẩn của ngôn ngữ lập trình C++. Nó chứa một số tính năng mới và các khái niệm, ví dụ:

  • tài liệu tham khảo giá trị
  • các loại giá trị biểu thức xvalue, glvalue, prvalue
  • di chuyển ngữ nghĩa

Nếu chúng ta muốn hiểu các khái niệm về các loại giá trị biểu thức mới, chúng ta phải nhận thức được rằng có các tham chiếu rvalue và lvalue. Tốt hơn là nên biết giá trị có thể được chuyển đến các tham chiếu giá trị không const.

int& r_i=7; // compile error
int&& rr_i=7; // OK

Chúng ta có thể có được một số trực giác về các khái niệm của các loại giá trị nếu chúng ta trích dẫn tiểu mục có giá trị Lvalues ​​và giá trị từ dự thảo làm việc N3337 (dự thảo tương tự nhất với tiêu chuẩn ISOC++ 11 đã xuất bản).

3.10 Giá trị và giá trị [basic.lval]

1 Biểu thức được phân loại theo phân loại trong Hình 1.

  • Một giá trị (được gọi là, trong lịch sử, bởi vì giá trị có thể xuất hiện ở phía bên trái của biểu thức gán) chỉ định một chức năng hoặc một đối tượng. [Ví dụ: Nếu E là biểu thức của loại con trỏ, thì * E là biểu thức giá trị tham chiếu đến đối tượng hoặc hàm mà E trỏ tới. Một ví dụ khác, kết quả của việc gọi một hàm có kiểu trả về là tham chiếu giá trị là một giá trị. Ví dụ
  • Một giá trị xvalue (một giá trị của eXpires ') cũng đề cập đến một đối tượng, thường là gần cuối vòng đời của nó (ví dụ như tài nguyên của nó có thể được di chuyển). Một xvalue là kết quả của một số loại biểu thức liên quan đến các tham chiếu rvalue (8.3.2). [Ví dụ: Kết quả của việc gọi một hàm có kiểu trả về là tham chiếu rvalue là xvalue. Ví dụ
  • Một giá trị (giá trị chung của Lvalue) là giá trị hoặc giá trị xvalue.
  • Một giá trị (được gọi là, theo lịch sử, bởi vì các giá trị có thể xuất hiện ở phía bên phải của biểu thức gán) là một giá trị x,
    [.__.] đối tượng tạm thời (12.2) hoặc phụ đề của chúng hoặc giá trị không phải là
    [.__.] liên kết với một đối tượng.
  • Một giá trị (giá trị thuần túy rvalue) là một giá trị không phải là một giá trị. [Ví dụ: Kết quả của việc gọi một hàm có kiểu trả về không phải là một
    [.__.] tham chiếu là một giá trị. Giá trị của một chữ như 12, 7.3e5 hoặc
    [.__.] đúng cũng là một giá trị. Ví dụ

Mỗi biểu thức thuộc về chính xác một trong các phân loại cơ bản trong phân loại này: lvalue, xvalue hoặc prvalue. Thuộc tính này của một biểu thức được gọi là thể loại giá trị của nó.

Nhưng tôi không chắc lắm về việc tiểu mục này đủ để hiểu các khái niệm rõ ràng, bởi vì "thông thường" không thực sự chung chung, "gần cuối đời" không thực sự cụ thể, "liên quan đến các tham chiếu giá trị" không thực sự rõ ràng, và "Ví dụ: Kết quả của việc gọi một hàm có kiểu trả về là tham chiếu giá trị là một giá trị x." Nghe có vẻ như một con rắn đang cắn đuôi nó.

THỂ LOẠI GIÁ TRỊ CHÍNH XÁC

Mỗi biểu thức thuộc về chính xác một loại giá trị chính. Các loại giá trị này là các loại lvalue, xvalue và prvalue.

giá trị

Biểu thức E thuộc danh mục lvalue khi và chỉ khi E đề cập đến một thực thể mà ALREADY đã có một danh tính (địa chỉ, tên hoặc bí danh) để có thể truy cập bên ngoài E.

#include <iostream>

int i=7;

const int& f(){
    return i;
}

int main()
{
    std::cout<<&"www"<<std::endl; // The expression "www" in this row is an lvalue expression, because string literals are arrays and every array has an address.  

    i; // The expression i in this row is an lvalue expression, because it refers to the same entity ...
    i; // ... as the entity the expression i in this row refers to.

    int* p_i=new int(7);
    *p_i; // The expression *p_i in this row is an lvalue expression, because it refers to the same entity ...
    *p_i; // ... as the entity the expression *p_i in this row refers to.

    const int& r_I=7;
    r_I; // The expression r_I in this row is an lvalue expression, because it refers to the same entity ...
    r_I; // ... as the entity the expression r_I in this row refers to.

    f(); // The expression f() in this row is an lvalue expression, because it refers to the same entity ...
    i; // ... as the entity the expression f() in this row refers to.

    return 0;
}

xvalues ​​

Biểu thức E thuộc danh mục xvalue khi và chỉ khi nó là

- kết quả của việc gọi một hàm, dù là ngầm hay rõ ràng, kiểu trả về của nó là một tham chiếu giá trị cho loại đối tượng được trả về, hoặc

int&& f(){
    return 3;
}

int main()
{
    f(); // The expression f() belongs to the xvalue category, because f() return type is an rvalue reference to object type.

    return 0;
}

- chuyển sang tham chiếu giá trị cho loại đối tượng hoặc

int main()
{
    static_cast<int&&>(7); // The expression static_cast<int&&>(7) belongs to the xvalue category, because it is a cast to an rvalue reference to object type.
    std::move(7); // std::move(7) is equivalent to static_cast<int&&>(7).

    return 0;
}

- biểu thức truy cập thành viên lớp chỉ định thành viên dữ liệu không tĩnh thuộc loại không tham chiếu trong đó biểu thức đối tượng là xvalue hoặc

struct As
{
    int i;
};

As&& f(){
    return As();
}

int main()
{
    f().i; // The expression f().i belongs to the xvalue category, because As::i is a non-static data member of non-reference type, and the subexpression f() belongs to the xvlaue category.

    return 0;
}

- biểu thức con trỏ đến thành viên trong đó toán hạng thứ nhất là xvalue và toán hạng thứ hai là con trỏ tới thành viên dữ liệu.

Lưu ý rằng tác dụng của các quy tắc ở trên là các tham chiếu rvalue có tên cho các đối tượng được coi là giá trị và tham chiếu rvalue không tên đối với các đối tượng được coi là xvalues; tham chiếu giá trị cho các hàm được coi là giá trị cho dù có tên hay không.

#include <functional>

struct As
{
    int i;
};

As&& f(){
    return As();
}

int main()
{
    f(); // The expression f() belongs to the xvalue category, because it refers to an unnamed rvalue reference to object.
    As&& rr_a=As();
    rr_a; // The expression rr_a belongs to the lvalue category, because it refers to a named rvalue reference to object.
    std::ref(f); // The expression std::ref(f) belongs to the lvalue category, because it refers to an rvalue reference to function.

    return 0;
}

giá trị

Biểu thức E thuộc về loại giá trị khi và chỉ khi E không thuộc về giá trị cũng không thuộc loại xvalue.

struct As
{
    void f(){
        this; // The expression this is a prvalue expression. Note, that the expression this is not a variable.
    }
};

As f(){
    return As();
}

int main()
{
    f(); // The expression f() belongs to the prvalue category, because it belongs neither to the lvalue nor to the xvalue category.

    return 0;
}

THỂ LOẠI GIÁ TRỊ HXN HỢP

Có hai loại giá trị hỗn hợp quan trọng hơn nữa. Các loại giá trị này là các loại giá trị và giá trị glvalue.

giá trị

Biểu thức E thuộc danh mục rvalue khi và chỉ khi E thuộc danh mục xvalue hoặc thuộc danh mục prvalue.

Lưu ý rằng định nghĩa này có nghĩa là biểu thức E thuộc về loại giá trị khi và chỉ khi E đề cập đến một thực thể không có bất kỳ danh tính nào làm cho nó có thể truy cập được bên ngoài E YET.

giá trị

Biểu thức E thuộc danh mục glvalue khi và chỉ khi E thuộc danh mục giá trị hoặc thuộc danh mục xvalue.

QUY TẮC THỰC HÀNH

Scott Meyer đã xuất bản một quy tắc rất hữu ích để phân biệt giá trị với giá trị.

  • Nếu bạn có thể lấy địa chỉ của một biểu thức, biểu thức là một giá trị.
  • Nếu loại biểu thức là một tham chiếu giá trị (ví dụ: T & hoặc const T &, v.v.), thì biểu thức đó là một giá trị.
  • Mặt khác, biểu thức là một giá trị. Về mặt khái niệm (và thường là trên thực tế), các giá trị tương ứng với các đối tượng tạm thời, chẳng hạn như các đối tượng được trả về từ các hàm hoặc được tạo thông qua các chuyển đổi kiểu ẩn. Hầu hết các giá trị theo nghĩa đen (ví dụ: 10 và 5,3) cũng là giá trị.
38
Dániel Sándor

Các danh mục của C++ 03 quá hạn chế để nắm bắt chính xác các tham chiếu giá trị vào các thuộc tính biểu thức.

Với sự giới thiệu của họ, người ta nói rằng một tham chiếu rvalue chưa được đặt tên sẽ đánh giá một giá trị, sao cho độ phân giải quá tải sẽ thích các ràng buộc tham chiếu giá trị hơn, điều này sẽ khiến nó chọn di chuyển các hàm tạo trên các hàm tạo sao chép. Nhưng nó đã được tìm thấy rằng điều này gây ra vấn đề xung quanh, ví dụ với Loại động và với trình độ.

Để hiển thị điều này, hãy xem xét

int const&& f();

int main() {
  int &&i = f(); // disgusting!
}

Trên các bản nháp trước xvalue, điều này được cho phép, bởi vì trong C++ 03, các giá trị của các loại không thuộc lớp không bao giờ đủ điều kiện cv. Nhưng điều này được dự định là const áp dụng trong trường hợp tham chiếu giá trị, bởi vì ở đây chúng tôi do đề cập đến các đối tượng (= bộ nhớ!) Và việc bỏ const từ các giá trị không thuộc lớp là chủ yếu cho lý do mà không có đối tượng xung quanh.

Vấn đề cho các loại năng động có bản chất tương tự. Trong C++ 03, các giá trị của loại lớp có một kiểu động đã biết - đó là kiểu tĩnh của biểu thức đó. Bởi vì để có một cách khác, bạn cần các tài liệu tham khảo hoặc các cuộc hội thảo, để đánh giá một giá trị. Điều đó không đúng với các tham chiếu giá trị không tên, nhưng chúng có thể hiển thị hành vi đa hình. Vì vậy, để giải quyết nó,

  • tham chiếu rvalue chưa được đặt tên trở thành xvalues ​​. Họ có thể đủ điều kiện và có khả năng có loại năng động khác nhau. Họ, như dự định, thích các tham chiếu giá trị trong quá tải, và sẽ không liên kết với các tham chiếu giá trị không phải là const.

  • Những gì trước đây là một giá trị (nghĩa đen, các đối tượng được tạo bởi phôi thành các loại không tham chiếu) giờ trở thành một giá trị . Họ có cùng sở thích với xvalues ​​trong quá tải.

  • Những gì trước đây là một lvalue vẫn là một giá trị.

Và hai nhóm được thực hiện để nắm bắt những nhóm có thể đủ điều kiện và có thể có các loại động khác nhau ( glvalues ​​) và những nhóm quá tải thích liên kết tham chiếu giá trị ( giá trị ).

34

Tôi đã vật lộn với điều này trong một thời gian dài, cho đến khi tôi bắt gặp lời giải thích của cppreference.com về loại giá trị .

Nó thực sự khá đơn giản, nhưng tôi thấy rằng nó thường được giải thích theo cách khó nhớ. Ở đây nó được giải thích rất sơ đồ. Tôi sẽ trích dẫn một số phần của trang:

Danh mục chính

Các loại giá trị chính tương ứng với hai thuộc tính của biểu thức:

  • có danh tính : có thể xác định biểu thức có tham chiếu cùng thực thể với biểu thức khác hay không, chẳng hạn như bằng cách so sánh địa chỉ của các đối tượng hoặc các chức năng mà chúng xác định (thu được trực tiếp hoặc gián tiếp);

  • có thể được di chuyển từ : di chuyển hàm tạo, di chuyển toán tử gán hoặc quá tải hàm khác thực hiện ngữ nghĩa di chuyển có thể liên kết với biểu thức.

Biểu hiện rằng:

  • có danh tính và không thể được di chuyển từ được gọi là biểu thức giá trị ;
  • có danh tính và có thể được di chuyển từ được gọi là biểu thức xvalue ;
  • không có danh tính và có thể được di chuyển từ được gọi là biểu thức prvalue ;
  • không có danh tính và không thể được chuyển từ không được sử dụng.

giá trị

Biểu thức lvalue ("giá trị bên trái") là một biểu thức mà có danh tính không thể được di chuyển từ .

rvalue (cho đến C++ 11), prvalue (kể từ C++ 11)

Biểu thức prvalue ("rvalue thuần túy") là một biểu thức mà không có danh tính có thể được di chuyển từ .

giá trị x

Biểu thức xvalue ("giá trị hết hạn") là một biểu thức mà có danh tính có thể được di chuyển từ .

giá trị

Một biểu thức glvalue ("lvalue tổng quát") là một biểu thức là một giá trị hoặc một giá trị xvalue. Nó có danh tính . Nó có thể hoặc không thể được di chuyển từ.

giá trị (kể từ C++ 11)

Biểu thức rvalue ("giá trị đúng") là một biểu thức có giá trị hoặc giá trị x. Nó có thể được di chuyển từ . Nó có thể có hoặc không có bản sắc.

24
Felix Dombek

Làm thế nào để các danh mục mới này liên quan đến các loại giá trị hiện tại và giá trị hiện tại?

Giá trị C++ 03 vẫn là giá trị C++ 11, trong khi đó giá trị C++ 03 được gọi là giá trị trong C++ 11.

16
fredoverflow

Một phụ lục cho các câu trả lời xuất sắc ở trên, về một điểm khiến tôi bối rối ngay cả sau khi tôi đã đọc Stroustrup và nghĩ rằng tôi hiểu sự khác biệt về giá trị/giá trị. Khi bạn thấy

int&& a = 3,

rất hấp dẫn khi đọc int&& dưới dạng một loại và kết luận rằng a là một giá trị. Không phải:

int&& a = 3;
int&& c = a; //error: cannot bind 'int' lvalue to 'int&&'
int& b = a; //compiles

a có tên và thực tế là một giá trị. Đừng nghĩ về && như một phần của loại a; nó chỉ là thứ cho bạn biết a được phép liên kết với cái gì.

Điều này đặc biệt quan trọng đối với các đối số loại T&& trong các hàm tạo. Nếu bạn viết

Foo::Foo(T&& _t) : t{_t} {}

bạn sẽ sao chép _t vào t. Bạn cần

Foo::Foo(T&& _t) : t{std::move(_t)} {} nếu bạn muốn di chuyển. Trình biên dịch của tôi có cảnh báo tôi không khi tôi bỏ move!

14
Mohan

Vì các câu trả lời trước đã bao quát toàn bộ lý thuyết đằng sau các loại giá trị, chỉ có một điều tôi muốn thêm vào: bạn thực sự có thể chơi với nó và kiểm tra nó.

Đối với một số thử nghiệm thực hành với các loại giá trị, bạn có thể sử dụng công cụ xác định khai báo . Hành vi của nó phân biệt rõ ràng giữa ba loại giá trị chính (xvalue, lvalue và prvalue).

Sử dụng bộ tiền xử lý giúp chúng ta tiết kiệm một số thao tác gõ ...

Danh mục chính:

#define IS_XVALUE(X) std::is_rvalue_reference<decltype((X))>::value
#define IS_LVALUE(X) std::is_lvalue_reference<decltype((X))>::value
#define IS_PRVALUE(X) !std::is_reference<decltype((X))>::value

Danh mục hỗn hợp:

#define IS_GLVALUE(X) IS_LVALUE(X) || IS_XVALUE(X)
#define IS_RVALUE(X) IS_PRVALUE(X) || IS_XVALUE(X)

Bây giờ chúng ta có thể sao chép (gần như) tất cả các ví dụ từ cppreference trên danh mục giá trị .

Dưới đây là một số ví dụ với C++ 17 (cho terse static_assert):

void doesNothing(){}
struct S
{
    int x{0};
};
int x = 1;
int y = 2;
S s;

static_assert(IS_LVALUE(x));
static_assert(IS_LVALUE(x+=y));
static_assert(IS_LVALUE("Hello world!"));
static_assert(IS_LVALUE(++x));

static_assert(IS_PRVALUE(1));
static_assert(IS_PRVALUE(x++));
static_assert(IS_PRVALUE(static_cast<double>(x)));
static_assert(IS_PRVALUE(std::string{}));
static_assert(IS_PRVALUE(throw std::exception()));
static_assert(IS_PRVALUE(doesNothing()));

static_assert(IS_XVALUE(std::move(s)));
// The next one doesn't work in gcc 8.2 but in gcc(trunk). Clang 7.0.0 and msvc 19.16 are doing fine.
static_assert(IS_XVALUE(S().x)); 

Các loại hỗn hợp là loại nhàm chán một khi bạn tìm ra loại chính.

Để biết thêm một số ví dụ (và thử nghiệm), hãy xem liên kết sau trên trình biên dịch Explorer . Đừng bận tâm đọc hội, mặc dù. Tôi đã thêm rất nhiều trình biên dịch chỉ để đảm bảo nó hoạt động trên tất cả các trình biên dịch phổ biến.

2
thebrandre