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


1356

Trong C ++ 03, một biểu thức là một trong hai một rvalue hoặc một giá trị trái .

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

  1. giá trị
  2. giá trị
  3. giá trị x
  4. giá trị
  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ị 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 các vị thần WG21 chỉ đang cố gắng làm chúng ta bối rối không?

9
@Philip Potter: Trong C ++ 03? Đúng. Một giá trị có thể được sử dụng như một giá trị vì có một chuyển đổi giá trị chuẩn thành giá trị chuẩn.
James McNellis

14
@Tyler: "Nếu bạn có thể gán cho nó, đó là một giá trị, nếu không, đó là một giá trị." -> Sai, bạn có thể gán cho giá trị lớp : string("hello") = string("world").
dòng chảy

4
Lưu ý rằng đây là danh mục giá trị. Có nhiều tính chất mà biểu thức có thể có. Chúng bao gồm trường bit (đúng / sai), tạm thời (đúng / sai) và loại (loại của nó).
Julian Schaub - litb

30
Tôi nghĩ rằng liên kết của Fred ở trên là tốt hơn bất kỳ câu trả lời nào ở đây. Các liên kết đã chết, mặc dù. Nó đã được chuyển đến: stroustrup.com/terminology.pdf
R. Martinho Fernandes

74
trong C ++, ngay cả các loại của bạn cũng có loại
nielsbot

Câu trả lời:


634

Tôi đoán tài liệu này có thể phục vụ như một 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 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 Elà một 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 điểm. 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 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. [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à xvalue.]
  • Một giá trị (giá trị chung của Lvalue ) là một giá trị hoặc một giá trị xvalue .
  • Một rvalue (cái gọi là, về mặt lịch sử, bởi vì rvalues có thể xuất hiện ở phía bên tay phải của một biểu thức gán) là một Xvalue, 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 kết hợp 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ị 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ị]

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.


Cảm ơn, câu trả lời này thực sự hữu ích! Nhưng trình biên dịch của tôi không đồng ý với các ví dụ của bạn về xvalues ​​và prvalues; họ hoàn toàn ngược lại Trả về bằng tham chiếu rvalue cho tôi một giá trị và trả về theo giá trị cho tôi một giá trị x. Bạn đã làm cho chúng lẫn lộn, hoặc giường thử nghiệm của tôi bị hỏng? Tôi đã thử điều này với GCC 4.6.1, tiếng kêu (từ svn) và MSVC, và tất cả chúng đều thể hiện cùng một hành vi.
Kim Gräsman

Rất tiếc, tôi chỉ theo liên kết và nhận thấy rằng các ví dụ có trong nguồn. Tôi sẽ đi tìm bản sao tiêu chuẩn của mình và kiểm tra xem nó nói gì ...
Kim Gräsman

4
Tôi sử dụng các macro từ đây để kiểm tra các biểu thức khác nhau: stackoverflow.com/a/6114546/96963 Có thể là họ đã chẩn đoán sai mọi thứ.
Kim Gräsman

1
Thêm xvalue không dành cho ngữ nghĩa di chuyển. Chỉ với cả lvalue và rvalue, ngữ nghĩa di chuyển, chuyển tiếp hoàn hảo và tham chiếu rvalue vẫn hoạt động tốt. Tôi nghĩ rằng xvalue chỉ dành cho toán tử dectype: nếu biểu thức toán hạng là xvalue, thì dectype đưa ra kiểu tham chiếu rvalue.
phối tử

1
@MuhamedCicak "Mọi biểu thức đều là giá trị hoặc giá trị": đó là sự thật; và tiêu chuẩn (hoặc tài liệu n3055) không nói là sai. Lý do câu này bị gạch bỏ là vì bạn đang xem xét các thay đổi giữa hai phiên bản của tài liệu. Câu đã bị xóa vì nó trở nên thừa thãi sau khi một lời giải thích chính xác hơn được thêm vào.
tối đa

337

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

Các FCD (n3092) có một mô tả tuyệt vời:

- 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). Xvalue là kết quả của một số loại biểu thức liên quan đến 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 giá trị 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 chúng 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ị x. [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 tích hợp 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à tạo ra một giá trị như kết quả. 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ị và giá trị hiện tại?

Lần nữa:

Phân loại

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ợ.


54
Tôi thích sơ đồ ở đây. Tôi nghĩ rằng có thể hữu ích khi bắt đầu câu trả lời với "Mọi biểu thức thuộc về chính xác một trong những phân loại cơ bản trong phân loại này: lvalue, xvalue hoặc prvalue." Sau đó, thật dễ dàng để sử dụng biểu đồ để hiển thị ba lớp cơ bản được kết hợp để tạo ra giá trị và giá trị.
Aaron McDaid

2
"Là glvalue" tương đương với "không phải là giá trị" và "là giá trị" tương đương với "không phải là giá trị".
Vladimir Reshetnikov

2
Cái này giúp tôi nhiều nhất: bajamircea.github.io/assets/2016-04-07-move-forward/... (sơ đồ Venn chủng loại giá trị)
John P

1
@AaronMcDaid Xin chào, câu hỏi nhanh nếu bạn / ai đó có thể trả lời ... Tại sao không đặt tên glvaluetheo lvaluelvaluenhư plvalue, để thống nhất?
Vijay Chavda

184

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ị 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 các giá trị thành hai nhóm nhỏ, xvalues ​​và prvalues, và chúng tôi đề cập đến giá trị và xvalues ​​là giá trị. Xvalues ​​là một loại thể loại 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
}

165

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:

Ngươi chỉ di chuyển khi không 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 để có rất nhiều tiềm năng cho việc di chuyển ngầm 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 theo đó 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 đã 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 là thế. Bạn đã chuyển một tham chiếu rvalue cho hàm tạo và do đó, nó liên kết với hàm tạo có tham chiếu rvalue. Đó 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 đáng &&lẽ phải là một đầu mối, nhưng điều đó không thay đổi sự thật rằng nó đã phá vỡ quy tắc. valkhô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ạ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à tạo ra valmộ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ị.

Một khi bạn làm điều đó, bạn không còn có thể nói điều đó 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ì vậy, những gì chúng ta gọi các tham chiếu rvalue không tên (giá trị trả về từ Functrê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 ta cần có khả năng di chuyển bằng cách trả lại một &&; 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::movetrở lại, sau tất cả. Đó không phải là một giá trị (kiểu cũ), bởi vì nó có thể ở 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 ý kiến ​​dưới đây). 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ừ việc nó được 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ị không phải là giá trị.

  • 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 di chuyển ngầm và xvalues ​​là các di chuyển rõ ràng ( std::movetrả về một giá trị xvalue).


11
@Thomas: Đó là một ví dụ; nó không quan trọng bằng cách nào nó tạo ra giá trị trả về. Điều quan trọng là nó trả về a &&.
Nicol Bolas

1
Lưu ý: các giá trị có thể nằm ở phía bên trái của một phương trình, cũng như - vì X foo(); foo() = X;... Vì lý do cơ bản này, tôi không thể làm theo câu trả lời xuất sắc ở trên cho đến hết, bởi vì bạn thực sự chỉ phân biệt giữa xvalue mới và prvalue kiểu cũ, dựa trên thực tế là nó có thể nằm trên lhs.
Dan Nissenbaum

1
Xlà một lớp học; X foo();là một khai báo hàm, và foo() = X();là một dòng mã. (Tôi đã bỏ tập hợp dấu ngoặc đơn thứ hai foo() = X();trong nhận xét trên của mình.) Đối với câu hỏi tôi vừa đăng với cách sử dụng này được tô sáng, hãy xem stackoverflow.com/questions/15482508/
Dan Nissenbaum 18/03/13

1
@DanNissenbaum "xvalue không thể ở phía bên trái của biểu thức gán" - tại sao không? Xem ideone.com/wyrxiT
Mikhail

1
Khai sáng câu trả lời. Đây chắc chắn là câu trả lời tốt nhất ở đây. Nó đã cho tôi lý do giới thiệu các loại giá trị mới và những gì đã xảy ra trước đó.
Nikos

136

IMHO, lời giải thích tốt nhất về ý nghĩa của nó đã cho chúng tôi Stroustrup + xem xét 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ị số âm - 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 tạo giá trị thành 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ó bản sắc
  • m: có thể được chuyển từ

Điều này dẫn tôi đến việc đưa sơ đồ này lên bảng: nhập mô tả hình ảnh ở đây

Đặ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 lvaluesvà hai điểm ở bên phải (được gắn nhãn mIm) là những người có ít nhiều hình thức đã gọi 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 các Wtên nên có liên quan đến lvaluevà "chân" bên phải của các Wtên nên liên quan đến rvalue.tôi lưu ý rằng toàn bộ cuộc thảo luận / vấn đề này phát sinh từ việc giới thiệu các tham chiếu giá trị và di chuyển ngữ nghĩa. Những khái niệm này đơn giản là không tồn tại trong thế giới của Strachey bao gồm chỉ rvalueslvalues. Có người quan sát rằng những ý tưởng đó

  • Mỗi cái valuelà một lvaluehoặc mộtrvalue
  • An lvaluekhông phải là an rvaluevà an rvaluekhô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 thấy rằng từ ngữ thư viện tiêu chuẩn sử dụng rvalueđể chỉ m(khái quát hóa), để duy trì sự kỳ vọng và văn bản của thư viện tiêu chuẩn, điểm dưới cùng bên phải của tên Wphải đượ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. Đầu tiên, chúng ta cần quyết định lvalue.nên lvaluecó nghĩa iMhay 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 nơi từ lvaluenày đủ đ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 lvaluehiện nay có nghĩa iM. Đây là ý nghĩa cổ điển của lvalue vì "ngày xưa" không có gì được di chuyển; movelà một khái niệm tiểu thuyết trong C++0x. Ngoài ra, việc đặt tên điểm topleft của W lvaluecung cấp cho chúng ta thuộc tính rằng mọi giá trị là một lvaluehoặ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 điểm Wlvaluevà đ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ó 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 cho 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 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 một lần nữa (ngoại trừ bởi một hàm hủy). Tôi thích cụm từ specialized rvaluetrái ngược với generalized lvaluenhưngpure rvalueviết tắt để prvaluegiành chiến thắng (và có lẽ đúng như vậy). Vì vậy, chân trái của W là lvalueglvaluechân phải là prvaluervalue.Ngẫu nhiên, mọi giá trị đều là giá trị hoặc giá trị, nhưng không phải cả hai.

Điều này để lạ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ó bất cứ điều gì hướng dẫn chúng tôi đến 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 khoe sản phẩm cuối cùng


14
vâng, tốt hơn là nên đọc các đề xuất và thảo luận ban đầu của danh hài C ++, hơn là tiêu chuẩn, nếu bạn muốn hiểu ý nghĩa của chúng: D
Ivan Kush

8
Văn học không có bản sắc và không thể được chuyển từ; dù sao chúng cũng hữu ích.
DrPizza

Tôi chỉ muốn làm rõ một điều. int && f () {return 1; } và MyClass && g () {return MyClass (); } trả về xvalue, phải không? Sau đó, tôi có thể tìm danh tính của các biểu thức f (); Và g();"? Họ có danh tính, bởi vì có một biểu thức khác trong câu lệnh return, đề cập đến cùng một đối tượng mà họ đề cập - tôi có hiểu đúng không?
Dániel Sándor

6
@DrPizza Theo tiêu chuẩn: chuỗi ký tự là lvalues, tất cả các chữ khác là prvalues. Nói một cách chính xác, bạn có thể đưa ra một lập luận để nói rằng những chữ không phải là chuỗi nên không thể di chuyển được, nhưng đó không phải là cách viết tiêu chuẩn.
Brian Vandenberg

59

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). Xvalue là kết quả của một số loại biểu thức liên quan đến 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 giá trị 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ị x, 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à một 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ó.

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 một cách 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 về loại giá trị 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) làm cho nó có thể truy cập được 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;
}

giá trị

Biểu thức E thuộc về loại 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;
}

- một biểu thức truy cập thành viên lớp chỉ định một 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à một giá trị x, 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;
}

- một biểu thức con trỏ đến thành viên trong đó toán hạng thứ nhất là một giá trị x và toán hạng thứ hai là một 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 đến các đối tượng được coi là giá trị và tham chiếu rvalue không tên cho 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 loại 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;
}

PHÂN LOẠI GIÁ TRỊ

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ị.

giá trị

Biểu thức E thuộc về loại giá trị khi và chỉ khi E thuộc loại xvalue hoặc thuộc loại 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 lvalue hoặc thuộc danh mục xvalue.

MỘT 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à 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ị.

3
Tất cả các ví dụ cho giá trị và tất cả các ví dụ cho xvalues ​​cũng là ví dụ cho giá trị. Cảm ơn bạn đã chỉnh sửa!
Dániel Sándor

1
Bạn đúng rồi. Ba loại giá trị chính được xác định. Rvalue cũng không cần thiết. Tôi nghĩ rằng rvalue và glvalue là tiêu chuẩn cho sự thuận tiện.
Dániel Sándor

1
Có một thời gian khó khăn để hiểu struct As{void f(){this;}}các thisbiến là một giá trị. Tôi nghĩ thisnên là một giá trị. Cho đến khi tiêu chuẩn 9.3.2 nói: Trong phần thân của hàm thành viên không tĩnh (9.3), từ khóa này là biểu thức prvalue.
r0ng

3
@ r0ng thislà một prvalue nhưng *thislà một giá trị trái
Xeverous

1
"www" không phải lúc nào cũng có cùng một địa chỉ. Nó là một giá trị vì nó là một mảng .
wally

35

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ị, điều này sẽ khiến nó chọn các hàm tạo di chuyển 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ụ như với Kiểu độ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 nó được thiết kế mà constáp dụng trong trường hợp rvalue-tài liệu tham khảo, bởi vì ở đây chúng tôi làm tham khảo đối tượng (= nhớ!), Và thả const từ rvalues phi lớp là chủ yếu vì lý do là 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 hội nghị, để đá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ó,

  • tài liệu tham khảo giá trị không 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) bây 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 ràng buộc tham chiếu giá trị (giá trị ).


1
Câu trả lời rõ ràng là hợp lý. xvalue chỉ là giá trị có thể đủ tiêu chuẩn cv và năng động!
phối tử

26

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 đi qua phần giải thích của cppreference.com về các 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 liệu biểu thức đề cập đến cùng một thực thể với một biểu thức khác, 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 constructor, 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 chuyển từ được gọi là biểu thức lvalue ;
  • có danh tính và có thể được chuyển từ được gọi là biểu thức xvalue ;
  • không có danh tính và có thể được 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 có danh tínhkhô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 không có danh tínhcó thể được di chuyển từ đó .

giá trị x

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

giá trị

Biểu thức glvalue ("lvalue tổng quát") là một biểu thức có giá trị hoặc giá trị xvalue. Nó có bản sắc . 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à 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.


1
Trong một số sách, xvalues ​​được hiển thị là x của họ đến từ "chuyên gia" hoặc "ngoại lệ"
noɥʇʎԀʎzɐɹƆ

Và quan trọng hơn, danh sách ví dụ toàn diện của họ.
Ciro Santilli 冠状 病毒 审查 事件

19

Làm thế nào để các danh mục mới này liên quan đến các loại giá trị 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.


14

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,

đọc nó int&&như một kiểu rất hấp dẫn và kết luận đó alà một giá trị. Nó không thể:

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

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

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

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

bạn sẽ sao chép _tvàot . 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 sẽ cảnh báo tôi khi tôi rời khỏi move!


1
Tôi nghĩ rằng câu trả lời này có thể được làm rõ. "Điều gì ađược phép liên kết với": Chắc chắn, nhưng trong dòng 2 & 3, các biến của bạn là c & b, và nó không phải là liên kết với, và loại akhông liên quan ở đây, phải không? Các dòng sẽ giống nhau nếu ađược khai báo int a. Sự khác biệt chính thực sự ở đây là trong dòng 1 không cần constphải liên kết với 3.
Felix Dombek

12

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ị, có một điều nữa 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 danh mục giá trị, bạn có thể sử dụng công cụ xác định dectype . 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 tôi 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 9.1. 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 thám hiểm trình biên dịch . Đừng bận tâm đọc hội đồng, 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.


Tôi nghĩ rằng #define IS_GLVALUE(X) IS_LVALUE(X) || IS_XVALUE(X)thực sự nên #define IS_GLVALUE(X) (IS_LVALUE(X) || IS_XVALUE(X))nhìn vào những gì xảy ra nếu &&hai bạn IS_GLVALUE.
Gabriel Devillers
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.