2012-08-26 20 views
10

Nếu bạn biên dịch chương trình này bằng trình biên dịch C++ 11, vectơ không được di chuyển ra khỏi hàm.Kết quả của toán tử bậc ba không phải là rvalue

#include <vector> 
using namespace std; 
vector<int> create(bool cond) { 
    vector<int> a(1); 
    vector<int> b(2); 

    return cond ? a : b; 
} 
int main() { 
    vector<int> v = create(true); 
    return 0; 
} 

Nếu bạn trả lại bản sao như thế này, nó sẽ được di chuyển.

if(cond) return a; 
else return b; 

Đây là demo on ideone.

Tôi đã thử với gcc 4.7.0 và MSVC10. Cả hai đều hành xử theo cùng một cách.
Tôi đoán tại sao điều này xảy ra là:
Loại toán tử bậc ba là một giá trị vì nó được đánh giá trước khi câu lệnh trả về được thực thi. Tại thời điểm này a và b chưa phải là xvalues ​​(sắp hết hạn).
Giải thích này có đúng không?

Đây có phải là lỗi trong tiêu chuẩn không?
Đây rõ ràng không phải là hành vi dự định và trường hợp rất phổ biến theo ý kiến ​​của tôi.

+0

"Nếu toán hạng thứ hai và thứ ba là giá trị của cùng một danh mục giá trị và có cùng loại, kết quả là loại danh mục và giá trị đó [...]" §5.16/4 – Mat

+0

Ví dụ không liên quan đến giá trị hoặc xvalues. Nhưng đó là một câu hỏi thú vị tại sao một bản sao được thực hiện thay vì di chuyển. – aschepler

Trả lời

8

Dưới đây là các dấu ngoặc kép có liên quan Tiêu chuẩn:

12,8 đoạn 32:

Sao chép sự bỏ bớt được phép trong các trường hợp sau đây [...]

  • trong một tuyên bố return trong một hàm có kiểu trả về lớp, khi biểu thức là tên của một đối tượng tự động không bay hơi (không phải là một hàm hoặc tham số điều khoản bắt) với các ame cv-không đủ tiêu chuẩn loại như các loại chức năng Đổi lại, các hoạt động sao chép/di chuyển có thể được bỏ qua bằng cách xây dựng các đối tượng tự động trực tiếp vào giá trị trả về của hàm
  • [khi throw ing, với điều kiện]
  • [khi nguồn là một tạm thời, với điều kiện]
  • [khi catch ing theo giá trị, với điều kiện]

đoạn 33:

Khi các tiêu chí cho phép cắt bỏ một hoạt động sao chép được đáp ứng hoặc sẽ được lưu lại cho thực tế là đối tượng nguồn là một tham số hàm, và đối tượng cần sao chép được chỉ định bởi một độ phân giải quá tải, lvalue để chọn hàm khởi tạo cho bản sao được thực hiện đầu tiên như thể đối tượng được chỉ định bởi một giá trị. Nếu độ phân giải quá tải thất bại, hoặc nếu kiểu tham số đầu tiên của hàm tạo đã chọn không phải là tham chiếu rvalue đối với kiểu của đối tượng (có thể là cv-qualified), thì độ phân giải quá tải được thực hiện lại, xem xét đối tượng như một giá trị.[Lưu ý: Độ phân giải quá tải hai giai đoạn này phải được thực hiện bất kể việc quét bản sao có xảy ra hay không. Nó xác định constructor được gọi nếu không thực hiện elision, và constructor đã chọn phải được truy cập ngay cả khi cuộc gọi được elided. - cuối note]

Kể từ khi biểu hiện trong return (cond ? a : b); không phải là một tên biến đơn giản, đó là không đủ điều kiện cho bản sao sự bỏ bớt hoặc điều trị rvalue. Có lẽ một chút không may, nhưng thật dễ dàng để tưởng tượng kéo dài ví dụ thêm một chút tại một thời điểm cho đến khi bạn tạo ra một nhức đầu của một kỳ vọng cho việc triển khai trình biên dịch.

Tất nhiên bạn có thể nhận được tất cả điều này bằng cách nói rõ ràng với std::move giá trị trả lại khi bạn biết nó an toàn.

+0

Vì vậy, bạn nói rằng đoạn 33 là điều duy nhất trong tiêu chuẩn mà một giá trị có thể được chuyển đến giá trị trả về, và do đó thực tế là đoạn 33 không áp dụng cho câu lệnh trả về này (trên tài khoản của nó không đủ điều kiện cho elision và không phải là một tham số chức năng), có nghĩa là nó không thể được di chuyển? Không phải là tôi không tin điều này, tôi chỉ không chắc tôi đã theo đuổi lý luận. –

+0

@SteveJessop: Điều đó nghe có vẻ đúng. Nếu không có 12.8p32-33, hành vi đúng duy nhất sẽ là sao chép vào giá trị trả về. Với p32 và không có p33, việc triển khai có thể chọn giữa việc sao chép hoặc bỏ qua bản sao. Với cả p32 và p33 (thực tế), việc thực hiện có thể lựa chọn thay vì di chuyển hoặc di chuyển. Việc triển khai không bao giờ được lựa chọn giữa việc sao chép và di chuyển: độ phân giải quá tải và ngoại lệ đặc biệt này sẽ giải thích điều gì sẽ xảy ra nếu không được elided. – aschepler

7

này sẽ sửa chữa nó

return cond ? std::move(a) : std::move(b); 

Hãy xem xét các nhà điều hành ternary như một chức năng, giống như mã của bạn là

return ternary(cond, a, b); 

Các thông số sẽ không được di chuyển ngầm, bạn cần phải làm cho nó rõ ràng.

+1

'return std :: move (cond? A: b); 'cũng hoạt động. Điều đó có tương đương không? –

+0

@AaronMcDaid: Có. – GManNickG

+0

đây là câu trả lời đúng. – Walter