2012-04-01 29 views
18

Sau ngày từ một bình luận tôi đã thực hiện về vấn đề này:người dân địa phương được trả về tự động xvalues ​​

passing std::vector to constructor and move semanticsstd::move cần thiết trong đoạn mã sau, để đảm bảo rằng giá trị trả về là một Xvalue?

std::vector<string> buildVector() 
{ 
    std::vector<string> local; 

    // .... build a vector 

    return std::move(local); 
} 

Tôi hiểu rằng điều này là bắt buộc. Tôi thường thấy này được sử dụng khi trả lại một std::unique_ptr từ một chức năng, tuy nhiên GManNickG đưa ra nhận định sau đây:

Đó là sự hiểu biết của tôi rằng trong một tuyên bố trở lại tất cả các biến địa phương sẽ được tự động xvalues ​​(đáo hạn giá trị) và sẽ được chuyển , nhưng tôi không chắc liệu điều đó chỉ áp dụng cho chính đối tượng được trả về hay không. Vì vậy, OP nên đi trước và đặt đó trong đó cho đến khi tôi tự tin hơn nó không cần phải được. :)

Có ai có thể làm rõ nếu std::move là cần thiết không?

Trình biên dịch hành vi có phụ thuộc không?

+0

Lưu ý bạn đã gây ra cho tôi để kể từ khi sửa đổi tuyên bố của tôi. Nó chỉ là giá trị trả về được di chuyển (có thể là biến cục bộ), không phải tất cả các biến cục bộ nói chung. (Mặc dù điều đó sẽ tốt đẹp, nó có thể phá vỡ trên một số mã cũ tôi không thể nghĩ đến, và C + + tiến triển đã duy trì khả năng tương thích ngược.) – GManNickG

Trả lời

14

Bạn được đảm bảo rằng local sẽ được trả lại dưới dạng giá trị trong trường hợp này. Thông thường các trình biên dịch sẽ thực hiện tối ưu hóa giá trị trả về mặc dù trước đây thậm chí trở thành một vấn đề, và bạn có thể sẽ không thấy bất kỳ động thái thực tế nào, vì đối tượng local sẽ được xây dựng trực tiếp tại trang gọi.

Một liên quan Note trong 6.6.3 [ "Lệnh return"] (2):

Một bản sao hoặc di chuyển hoạt động gắn liền với một câu lệnh return chưa elided hoặc coi là một rvalue với mục đích độ phân giải quá tải khi chọn một hàm tạo (12.8).

Để làm rõ, điều này là để nói rằng đối tượng trả về có thể được di chuyển xây dựng từ đối tượng địa phương (mặc dù trong thực tế RVO sẽ bỏ qua bước này hoàn toàn). Phần chuẩn của tiêu chuẩn là 12.8 ["Sao chép và di chuyển đối tượng lớp"] (31, 32), trên bản sao elision và rvalues ​​(nhờ @Mankarse!).


Dưới đây là một ví dụ ngớ ngẩn:

#include <utility> 

struct Foo 
{ 
    Foo()   = default; 
    Foo(Foo const &) = delete; 
    Foo(Foo &&)  = default; 
}; 

Foo f(Foo & x) 
{ 
    Foo y; 

    // return x;   // error: use of deleted function ‘Foo::Foo(const Foo&)’ 
    return std::move(x); // OK 
    return std::move(y); // OK 
    return y;   // OK (!!) 
} 

Contrast này với trả lại một tài liệu tham khảo rvalue thực tế:

Foo && g() 
{ 
    Foo y; 
    // return y;   // error: cannot bind ‘Foo’ lvalue to ‘Foo&&’ 
    return std::move(y); // OK type-wise (but undefined behaviour, thanks @GMNG) 
} 
+0

+1 (Để được rõ ràng, người cuối cùng có UB và chỉ nên được xem như là một ví dụ kiểm tra loại.) – GManNickG

+0

@GManNickG: True; bạn không thực sự trả về '&&' từ bất cứ thứ gì ngoài 'forward' và' move'. Đó là một ví dụ giả tạo. –

+0

Đoạn quy định đảm bảo hành vi này là '[class.copy]/32'. – Mankarse

4

Tôi nghĩ câu trả lời là không.Mặc dù đã chính thức chỉ là một lưu ý, §5/6 tóm tắt những gì biểu hiện được/không xvalues:

Một biểu thức là một Xvalue nếu nó là:

  • kết quả của cách gọi một chức năng, cho dù ngầm hoặc rõ ràng, có kiểu trả về là tham chiếu rvalue đối với loại đối tượng,
  • một phép đúc tham chiếu rvalue cho loại đối tượng,
  • 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 của loại không tham chiếu biểu thức đối tượng là xvalue hoặc
  • a. * Biểu thức con trỏ đến thành viên trong đó toán hạng đầu tiên là một xvalue và toán hạng thứ hai là một con trỏ tới thành viên dữ liệu.

Nói chung, hiệu ứng của quy tắc này là các tham chiếu rvalue được đặt tên được coi là giá trị và tham chiếu rvalue chưa đặt tên cho các đối tượng được coi là xvalues; tham chiếu rvalue cho các hàm được coi là giá trị cho dù được đặt tên hay không.

Điểm đầu tiên có vẻ như được áp dụng tại đây. Vì hàm trong câu hỏi trả về một giá trị chứ không phải là tham chiếu rvalue, kết quả sẽ không phải là một xvalue.

+0

+1 để nói về xvalues, bởi vì tôi đã sai ở đó. (Không phải ngày của tôi tôi giả sử.) May mắn thay, những gì thực sự quan trọng là nếu giá trị trả lại có thể được coi là một giá trị, đó là. Nhưng không phải tất cả xvalues ​​nói chung như tôi tuyên bố. – GManNickG

7

Đủ cả hai, return std::move(local)return local, làm việc theo nghĩa là chúng biên dịch, hành vi của chúng khác nhau. Và có lẽ chỉ có cái sau được dự định.

Nếu bạn viết một hàm trả về số std::vector<string>, bạn phải trả lại std::vector<string> và chính xác. std::move(local) có loại std::vector<string>&&không a std::vector<string> do đó phải được chuyển đổi sang sử dụng hàm khởi tạo di chuyển.

Tiêu chuẩn nói trong 6.6.3.2:

Giá trị của biểu thức là ngầm chuyển đổi sang kiểu trả về của hàm, trong đó nó xuất hiện.

Điều đó có nghĩa, return std::move(local) được equalvalent để

std::vector<std::string> converted(std::move(local); // move constructor 
return converted; // not yet a copy constructor call (which will be elided anyway) 

trong khi return local chỉ là

return local; // not yet a copy constructor call (which will be elided anyway) 

này phụ tùng bạn một thao tác.


Để cung cấp cho bạn một ví dụ ngắn về những gì có nghĩa là:

struct test { 
    test() { std::cout << " construct\n"; } 
    test(const test&) { std::cout << " copy\n"; } 
    test(test&&) { std::cout << " move\n"; } 
}; 

test f1() { test t; return t; } 
test f2() { test t; return std::move(t); } 

int main() 
{ 
    std::cout << "f1():\n"; test t1 = f1(); 
    std::cout << "f2():\n"; test t2 = f2(); 
} 

chí này ra

f1(): 
    construct 
f2(): 
    construct 
    move 
+0

Cảm ơn bạn đã nhập. – mark

+0

Lưu ý rằng điều này chỉ đúng với sự hiện diện của NVRO/elision. Nếu không có elision, 'f1' cũng sẽ phải di chuyển. –

+0

@NicolBolas: Vâng, đó là "chưa được một nhà xây dựng bản sao gọi" một phần được viết ở trên (tốt, thực sự nó phải là cuộc gọi nhà xây dựng di chuyển). Tuy nhiên, điều này ảnh hưởng đến cả hai 'f1' và' f2', do đó, ngay cả khi 'f1' có một hàm tạo gọi ít hơn. – ipc

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