2011-11-23 27 views
30

Tôi đã thử các cách sau:Làm thế nào để chụp một unique_ptr thành một biểu thức lambda?

std::function<void()> getAction(std::unique_ptr<MyClass> &&psomething){ 
    //The caller given ownership of psomething 
    return [psomething](){ 
     psomething->do_some_thing(); 
     //psomething is expected to be released after this point 
    }; 
} 

Nhưng nó không biên dịch. Bất kỳ ý tưởng?

UPDATE:

AS đề nghị, một số cú pháp mới là cần thiết để xác định một cách rõ ràng chúng ta cần phải chuyển quyền sở hữu cho lambda, bây giờ tôi đang suy nghĩ về cú pháp sau:

std::function<void()> getAction(std::unique_ptr<MyClass> psomething){ 
    //The caller given ownership of psomething 
    return [auto psomething=move(psomething)](){ 
     psomething->do_some_thing(); 
     //psomething is expected to be released after this point 
    }; 
} 

Nó sẽ là một ứng cử viên tốt?

UPDATE 1:

tôi sẽ giới thiệu thực hiện của tôi movecopy như sau:

template<typename T> 
T copy(const T &t) { 
    return t; 
} 

//process lvalue references 
template<typename T> 
T move(T &t) { 
    return std::move(t); 
} 

class A{/*...*/}; 

void test(A &&a); 

int main(int, char **){ 
    A a; 
    test(copy(a)); //OK, copied 
    test(move(a)); //OK, moved 
    test(A());  //OK, temporary object 
    test(copy(A())); //OK, copying temporary object 
    //You can disable this behavior by letting copy accepts T & 
    //test(move(A())); You should never move a temporary object 
    //It is not good to have a rvalue version of move. 
    //test(a); forbidden, you have to say weather you want to copy or move 
    //from a lvalue reference. 
} 

Trả lời

46

Vấn đề này được giải quyết bằng lambda generalized capture trong C++ 14:

// a unique_ptr is move-only 
auto u = make_unique<some_type>(some, parameters); 

// move the unique_ptr into the lambda 
go.run([u = move(u)]{do_something_with(u);}); 
+0

Đó là giải pháp lý tưởng. –

+0

Khi nào con trỏ duy nhất được phát hành trong những trường hợp này? Khi lambda làm gì? – Leo

+0

@Leo Con trỏ duy nhất phải được di chuyển đến khối dữ liệu của lambda (một lambda chỉ là một đối tượng với toán tử '()') và sau đó được giải phóng sau khi đối tượng lambda được phát hành. Đối tượng lambda như là một rvalue có thể được giải phóng sau 'go.run' nếu nó không được di chuyển. Nếu nó được di chuyển nó có thể được phát hành bất cứ lúc nào sau nó, và điều này có ý nghĩa nếu bạn muốn lên lịch 'do_something_with' để chạy sau này. –

31

Bạn có thể không vĩnh viễn chụp một unique_ptr trong một lambda. Thật vậy, nếu bạn muốn chụp vĩnh viễn bất kỳ thứ gì trong một lambda, nó phải là có thể sao chép; chỉ đơn thuần là di chuyển là không đủ.

Điều này có thể được coi là lỗi trong C++ 11, nhưng bạn sẽ cần một số cú pháp để nói rõ ràng rằng bạn muốn di chuyển giá trị unique_ptr vào lambda. Đặc điểm kỹ thuật C++ 11 được viết rất cẩn thận để ngăn chặn các chuyển động tiềm ẩn trên các biến được đặt tên; đó là lý do tại sao std::move tồn tại và đây là một điều tốt.

Để thực hiện những gì bạn muốn, hãy yêu cầu sử dụng std::bind (sẽ được bán phức tạp, yêu cầu một chuỗi ngắn binds) hoặc chỉ trả về một đối tượng cũ thông thường.

Ngoài ra, không bao giờ lấy unique_ptr bởi &&, trừ khi bạn đang thực sự viết hàm khởi tạo của nó. Chỉ lấy nó theo giá trị; cách duy nhất mà người dùng có thể cung cấp theo giá trị là với std::move. Thật vậy, nó thường là một ý tưởng tốt để không bao giờ lấy bất cứ điều gì bởi &&, trừ khi bạn đang viết toán tử khởi tạo/gán (hoặc thực hiện một hàm chuyển tiếp).

+0

Greate! Tôi muốn đánh dấu câu trả lời này là đúng nếu bạn cung cấp một số ví dụ về chuỗi 'bind'. –

+2

Tôi đồng ý rằng việc sử dụng 'unique_ptr' bởi' && 'không phải là một ý tưởng hay. Tuy nhiên, nói chung tôi tin rằng có một cái gì đó bởi '&&' có nghĩa là "Tôi muốn người gọi thực sự cho tôi quyền sở hữu của một cái gì đó, không chỉ để tham khảo". –

+0

@EarthEngine: Bạn có thể nhận được điều đó bằng cách lấy đối số bằng * value *. Nếu họ vượt qua một tạm thời, sau đó tạm thời sẽ được chuyển vào giá trị đối số của bạn. Nếu chúng vượt qua một không tạm thời, chúng vẫn phải sử dụng 'std :: move', điều này sẽ làm cho chuyển động xảy ra trong đối số * của bạn *. Cách bạn đang làm điều đó có nghĩa là chức năng của bạn không * có * để sở hữu nó. [Bài đăng của tôi ở đây] (http://stackoverflow.com/questions/8114276/how-do-i-pass-a-unique-ptr-argument-to-a-constructor-or-a-function/8114913#8114913) giải thích chi tiết hơn. –

18

Các "bán phức tạp" giải pháp sử dụng std::bind như đã đề cập bởi Nicol Bolas không phải là quá xấu sau khi tất cả:

std::function<void()> getAction(std::unique_ptr<MyClass>&& psomething) 
{ 
    return std::bind([] (std::unique_ptr<MyClass>& p) { p->do_some_thing(); }, 
        std::move(psomething)); 
} 
+2

Tôi đồng ý, có vẻ không tệ. –

+1

không biên dịch trong C++ 11 – ZivS

+0

Kể từ khi std :: chức năng được yêu cầu để có thể sao chép được, tôi không thể xem cách thức hoạt động của nó. – Catskul

7

Một giải pháp tối ưu phụ làm việc cho tôi là chuyển đổi unique_ptr thành shared_ptr và sau đó chụp shared_ptr trong lambda.

std::function<void()> getAction(std::unique_ptr<MyClass> psomething) 
{ 
    //The caller given ownership of psomething 
    std::shared_ptr<MyClass> psomethingShared = std::shared_ptr<MyClass>(std::move(psomething)); 
    return [psomethingShared]() 
    { 
     psomethingShared->do_some_thing(); 
    }; 
} 
0

tôi đã sử dụng workaround thực sự tinh ranh này, trong đó bao gồm gắn bó các unique_ptr bên trong một shared_ptr. Điều này là do mã của tôi yêu cầu unique_ptr (do hạn chế API) nên tôi không thể chuyển đổi nó thành shared_ptr (nếu không tôi không bao giờ có thể lấy lại được unique_ptr).

Lý do chính đáng của tôi để sử dụng tính năng hủy bỏ này là mã số thử nghiệm của tôi và tôi đã phải std::bind a unique_ptr vào cuộc gọi hàm thử nghiệm.

// Put unique_ptr inside a shared_ptr 
auto sh = std::make_shared<std::unique_ptr<Type>>(std::move(unique)); 

std::function<void()> fnTest = std::bind([this, sh, input, output]() { 
    // Move unique_ptr back out of shared_ptr 
    auto unique = std::move(*sh.get()); 

    // Make sure unique_ptr is still valid 
    assert(unique); 

    // Move unique_ptr over to final function while calling it 
    this->run_test(std::move(unique), input, output); 
}); 

Bây giờ gọi fnTest() sẽ gọi run_test() khi đi qua các unique_ptr với nó. Gọi fnTest() lần thứ hai sẽ dẫn đến lỗi xác nhận, vì unique_ptr đã bị di chuyển/bị mất trong lần gọi đầu tiên.

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