2016-09-30 14 views
13

Tôi đang cố gắng trả lại thành viên std::unique_ptr class (cố gắng chuyển quyền sở hữu) cho người gọi. Sau đây là một đoạn mã mẫu:Trả lại thành viên unique_ptr từ phương thức lớp học

class A { 
public: 
    A() : p {new int{10}} {} 

    static std::unique_ptr<int> Foo(A &a) { 
    return a.p; // ERROR: Copy constructor getting invoked 
       // return std::move(a.p); WORKS FINE 
    } 

    std::unique_ptr<int> p; 
}; 

Tôi nghĩ trình biên dịch (gcc-5.2.1) sẽ có thể làm tối ưu hóa giá trị trả về (bản sao sự bỏ bớt) trong trường hợp này mà không đòi hỏi mục đích rõ ràng qua std::move(). Nhưng đó không phải là trường hợp. Tại sao không?

Các mã sau đây dường như được làm việc tốt, mà dường như tương đương:

std::unique_ptr<int> foo() { 
    std::unique_ptr<int> p {new int{10}}; 
    return p; 
} 
+2

Đây là câu hỏi đầu tiên tuyệt vời. Chào mừng bạn đến với StackOverflow! – Barry

Trả lời

11

Các quy tắc trong [class.copy] là:

[...] khi biểu trong một tuyên bố return là (có thể là được ngoặc đơn) id-expression đặt tên cho đối tượng có thời lượng lưu trữ tự động được khai báo trong phần nội dung hoặc tham số -decla Điều khoản khẩu phần của hàm bao trong cùng hoặc biểu thức lambda, độ phân giải quá tải để chọn hàm 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ị.

Trong ví dụ này:

std::unique_ptr<int> foo() { 
    std::unique_ptr<int> p {new int{10}}; 
    return p; 
} 

p là tên của một đối tượng với thời gian lưu trữ tự động khai báo trong thân của hàm. Vì vậy, thay vì sao chép nó vào giá trị trả về, trước tiên chúng tôi cố gắng di chuyển nó. Điều đó hoạt động tốt.

Nhưng trong ví dụ này:

static std::unique_ptr<int> Foo(A &a) { 
    return a.p; 
} 

mà không áp dụng. a.p không phải là tên của một đối tượng nào cả, vì vậy chúng tôi không thử độ phân giải quá tải như thể nó là một giá trị, thay vào đó chúng ta chỉ làm điều bình thường: cố gắng sao chép nó. Điều này không thành công, vì vậy bạn phải rõ ràng move() nó.


Đó là từ ngữ của quy tắc nhưng có thể không trả lời được câu hỏi của bạn. Tại sao đây là quy tắc? Về cơ bản - chúng tôi đang cố gắng để được an toàn. Nếu chúng ta đặt tên một biến cục bộ, nó luôn an toàn để di chuyển từ nó trong một câu lệnh trả về. Nó sẽ không bao giờ được truy cập nữa. Tối ưu hóa dễ dàng, không có nhược điểm nào có thể xảy ra. Nhưng trong ví dụ ban đầu của bạn, a không thuộc sở hữu của chức năng này, không phải là a.p. Nó không an toàn để di chuyển từ nó, vì vậy ngôn ngữ sẽ không cố gắng làm điều đó tự động.

-1

Không thể áp dụng thao tác sao chép (trong số các lý do khác) vì a.pstd::unique_ptr, không thể sao chép được. Và kể từ khi a.p có tuổi thọ vượt quá cơ thể của A::Foo(A&), nó sẽ rất ngạc nhiên (như trong, đáng ngạc nhiên đối với người viết mã) nếu trình biên dịch tự động cố gắng di chuyển từ a.p, có khả năng phá hủy các bất biến lớp học a. Nó sẽ hoạt động nếu bạn return std::move(a.p);, nhưng sẽ đánh cắp rõ ràng a.p.

+0

Sao chép elision có thể áp dụng cho các loại di chuyển chỉ (ví dụ thứ hai của OP) – Barry

+0

@Barry OK, câu đầu tiên của tôi là sai. Sao chép elision cũng áp dụng cho di chuyển. Tuy nhiên, tôi tin rằng phần còn lại của câu trả lời vẫn đứng vững. –

+0

@AndreKostur: Vì trình coder đang cố gắng trả về một unique_ptr có hàm tạo bản sao bị xóa, tùy chọn duy nhất là để di chuyển xảy ra và sao chép elision sẽ hoạt động tốt (IMO). Điều khiến tôi ngạc nhiên là lỗi biên dịch chứ không phải là cảnh báo. – axg

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