2016-08-12 15 views
15

Tôi đã tự hỏi về hành vi C++ khi giá trị r được chuyển giữa các hàm.thông số giá trị r trong một hàm

Nhìn vào mã đơn giản này:

#include <string> 

void foo(std::string&& str) { 
    // Accept a rvalue of str 
} 

void bar(std::string&& str) { 
    // foo(str);   // Does not compile. Compiler says cannot bind lvalue into rvalue. 
    foo(std::move(str)); // It feels like a re-casting into a r-value? 
} 

int main(int argc, char *argv[]) { 
    bar(std::string("c++_rvalue")); 
    return 0; 
} 

Tôi biết khi tôi bên bar chức năng tôi cần phải sử dụng move chức năng để gọi foo chức năng. Câu hỏi của tôi bây giờ là tại sao?

Khi tôi bên trong bar chức năng biến str đã phải là một r-value, nhưng trình biên dịch hoạt động như nó là một l-giá trị.

Ai đó có thể trích dẫn một số tham chiếu đến tiêu chuẩn về hành vi này không? Cảm ơn!

+5

Đối tượng được đặt tên luôn là giá trị. Đó là lý do tại sao bạn cần 'di chuyển'. –

Trả lời

10

str là tham chiếu giá trị, nghĩa là chỉ là tham chiếu đến giá trị. Nhưng nó vẫn là một tham chiếu, đó là một giá trị. Bạn có thể sử dụng str làm biến số, điều này cũng ngụ ý rằng nó là một giá trị không phải là giá trị tạm thời.

Một vế trái là, theo §3.10.1.1:

Một vế trái (cái gọi là, về mặt lịch sử, bởi vì lvalues ​​có thể xuất hiện ở phía bên trái của một biểu thức phân) chỉ định một chức năng hoặc đối tượng.[Ví dụ: Nếu E là một biểu hiện của kiểu con trỏ, sau đó * E là một biểu hiện giá trị trái ám chỉ đến đối tượng hoặc chức năng 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 lvalue là một giá trị. - cuối dụ]

Và một rvalue là, theo §3.10.1.4:

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

Dựa trên điều này, str không phải là một đối tượng tạm thời, và nó kết hợp với một đối tượng (với đối tượng được gọi là str), và vì vậy nó không phải là một rvalue.

Ví dụ về lvalue sử dụng con trỏ, nhưng nó cũng giống với tham chiếu và tự nhiên cho tham chiếu rvalue (chỉ là một kiểu tham chiếu đặc biệt).

Vì vậy, trong ví dụ của bạn, str một vế trái, vì vậy bạn phải std::move nó để gọi foo (mà chỉ nhận rvalues, không lvalues).

7

Các "rvalue" trong "tài liệu tham khảo rvalue" dùng để chỉ các loại giá trị mà các tài liệu tham khảo có thể ràng buộc để:

  • vế trái tài liệu tham khảo có thể liên kết với lvalues ​​
  • tài liệu tham khảo rvalue thể liên kết với rvalues ​​
  • (+ nhiều hơn một chút)

Đó là tất cả. Quan trọng hơn, nó không tham chiếu đến giá trị nhận được khi bạn sử dụng tham chiếu. Khi bạn có biến tham chiếu (bất kỳ loại tham chiếu nào!), Thì việc đặt tên biểu thức id biến đó luôn là một giá trị. Giá trị xuất hiện trong tự nhiên chỉ dưới dạng giá trị tạm thời hoặc dưới dạng giá trị của biểu thức cuộc gọi hàm hoặc dưới dạng giá trị của biểu thức truyền hoặc kết quả phân rã hoặc của this. Có một tương tự nhất định ở đây với dereferencing một con trỏ: dereferencing một con trỏ luôn luôn là một lvalue, không có vấn đề làm thế nào mà con trỏ đã thu được: *p, *(p + 1), *f() là tất cả các giá trị. Nó không quan trọng làm thế nào bạn đến bởi điều này; một khi bạn có nó, nó là một giá trị.

Quay trở lại một chút, có lẽ khía cạnh thú vị nhất của tất cả điều này là tham chiếu rvalue là một cơ chế để chuyển đổi một giá trị thành một giá trị. Không có cơ chế như vậy đã tồn tại trước khi C++ 11 tạo ra các giá trị có thể thay đổi. Trong khi chuyển đổi từ lvalue sang rvalue là một phần của ngôn ngữ kể từ khi nó bắt đầu, nó mất nhiều thời gian hơn để khám phá sự cần thiết phải chuyển đổi rvalue-to-lvalue.

3

Câu hỏi của tôi bây giờ là lý do tại sao?

Tôi đang thêm một câu trả lời khác vì tôi muốn nhấn mạnh câu trả lời cho "lý do".

Mặc dù có tên là tham chiếu rvalue có thể liên kết với một giá trị, chúng được coi là giá trị khi được sử dụng. Ví dụ:

struct A {}; 

void h(const A&); 
void h(A&&); 

void g(const A&); 
void g(A&&); 

void f(A&& a) 
{ 
    g(a); // calls g(const A&) 
    h(a); // calls h(const A&) 
} 

Mặc dù một rvalue thể liên kết với các tham số của af(), một khi bị ràng buộc, a nay được coi là một giá trị trái. Đặc biệt, các cuộc gọi đến các hàm quá tải g()h() giải quyết cho quá tải const A& (lvalue). Xử a như một rvalue trong f sẽ dẫn đến mã dễ bị lỗi: Đầu tiên là "phiên bản di chuyển" của g() sẽ được gọi, mà có khả năng sẽ ăn cắp vặt a, và sau đó đánh cắp a sẽ được gửi đến quá tải di chuyển của h().

Reference.

Các vấn đề liên quan