2015-09-09 16 views
15

This answer giải thích cách di chuyển chụp một biến trong một lambda trong C++ 14.Di chuyển một lambda: một khi bạn đã di chuyển một loại di chuyển chỉ, làm thế nào có thể lambda được sử dụng?

Nhưng khi bạn đã di chuyển một đối tượng không thể sao chép được (chẳng hạn như std::unique_ptr) trong một lambda, bạn không thể sao chép chính lambda.

Đây sẽ là tốt nếu bạn có thể di chuyển lambda, nhưng tôi nhận được một lỗi biên dịch khi cố gắng làm như vậy:

using namespace std; 

class HasCallback 
{ 
    public: 
    void setCallback(std::function<void(void)>&& f) 
    { 
     callback = move(f); 
    } 

    std::function<void(void)> callback; 
}; 

int main() 
{ 
    auto uniq = make_unique<std::string>("Blah blah blah"); 
    HasCallback hc; 
    hc.setCallback(
     [uniq = move(uniq)](void) 
     { 
     std::cout << *uniq << std::endl; 
     }); 

    hc.callback(); 
} 

này tạo ra các lỗi sau với g++ (Tôi đã cố gắng để sao chép chỉ dòng thích hợp):

error: use of deleted function ‘main()::<lambda()>::<lambda>(const main()::<lambda()>&’ 

... ngụ ý, tôi nghĩ rằng nỗ lực di chuyển lambda của tôi không thành công.

clang++ đưa ra lỗi tương tự.

Tôi đã thử rõ ràng move nhập lambda (mặc dù đó là giá trị tạm thời), nhưng điều đó không giúp ích gì.

EDIT: Câu trả lời dưới đây giải quyết đầy đủ các lỗi biên dịch được tạo bởi mã trên. Đối với phương pháp thay thế, chỉ cần release giá trị mục tiêu của con trỏ duy nhất vào một số std::shared_ptr, trong đó có thể được sao chép. (Tôi không viết câu trả lời này, bởi vì điều đó sẽ cho rằng đây là vấn đề XY, nhưng lý do cơ bản tại sao unique_ptr không thể được sử dụng trong lambda được chuyển đổi thành std::function là điều quan trọng cần hiểu.)

EDIT 2: Hài hước đủ, tôi vừa nhận ra auto_ptr thực sự sẽ làm điều đúng ở đây (!), Theo như tôi có thể nói. Nó hoạt động cơ bản như unique_ptr, nhưng cho phép sao chép-xây dựng thay cho di chuyển-xây dựng.

+0

Tôi nghĩ setCallback sẽ nhận được thông số theo giá trị chứ không phải là tài liệu tham khảo rvalue, tôi sai? – Slava

+0

@Slava Đó là những gì tôi đã có ban đầu, nhưng nó đã cho cùng một lỗi. Tôi nghĩ lấy tham chiếu rvalue sẽ cho phép (/ force) lambda được di chuyển xây dựng, nhưng điều đó dường như không đúng. –

Trả lời

14

Bạn có thể di chuyển lambda, điều đó là tốt. Tuy nhiên, đó không phải là vấn đề của bạn, bạn đang cố gắng thực hiện một số std::function với một lambda không thể sao chép được. Và: constructor

template< class F > 
function(F f); 

của function làm:

5) Khởi mục tiêu với một bản sao của f.

Điều này là do std::function:

đáp ứng các yêu cầu của CopyConstructible và CopyAssignable.

function phải có thể sao chép được, mọi thứ bạn đưa vào cũng phải có thể sao chép được. Và một lambda chỉ di chuyển không đáp ứng yêu cầu đó.

+0

.... huh. Cảm ơn bạn-- Tôi đã có một thời gian rất khó phân tích các thông báo lỗi ngay cả đối với trường hợp đơn giản này. Có cách nào để thay đổi chữ ký để làm việc, ngắn bằng cách sử dụng một tham số mẫu phổ quát-ref-đủ điều kiện thay vì 'std :: function'? –

+0

@KyleStrand Không quan trọng tham số là gì, bạn không thể xây dựng 'hàm'. Nếu bạn cần một thứ gì đó bị xóa, bạn phải viết một 'hàm' tương đương có thể di chuyển được. – Barry

+0

@Barry có di chuyển chỉ std :: chức năng lựa chọn thay thế đã ra khỏi đó chỉ cần đề cập đến hai: https://github.com/Naios/Function2 và https://github.com/potswa/cxx_function –

10

std::function không phải là lambda!Đó là một wrapper có thể được xây dựng từ bất kỳ loại có thể gọi được, bao gồm một lambda. std::function yêu cầu rằng callable be copy-constructible, đó là lý do tại sao ví dụ của bạn không thành công.

Một lambda di chuyển chỉ có thể được di chuyển một lần nữa như được hiển thị bên dưới.

template<typename F> 
void call(F&& f) 
{ 
    auto f1 = std::forward<F>(f); // construct a local copy 
    f1(); 
} 

int main() 
{ 
    auto uniq = make_unique<std::string>("Blah blah blah"); 
    auto lambda = [uniq = move(uniq)]() { 
     std::cout << *uniq << std::endl; 
     }; 
// call(lambda); // doesn't compile because the lambda cannot be copied 
    call(std::move(lambda)); 
} 

Live demo

+0

Ack. Tôi biết 'std :: function' không phải là lambda, nhưng vì lambda có thể chuyển đổi thành' std :: function', tôi không giữ được sự phân biệt rõ ràng trong đầu. –

+0

Vậy làm cách nào để triển khai HasCallback trong mã gốc? Những gì tôi có nghĩa là, làm thế nào để lưu lambda di chuyển chỉ vào một di chuyển chỉ chứa hoặc di chuyển chỉ cấu trúc dữ liệu? – alpha

+0

@alpha Bạn sẽ cần phải sử dụng lambda trực tiếp mà không cần chuyển đổi nó sang kiểu khác (tức là hàm gọi lambda sẽ cần lấy lambda làm đối số kiểu mẫu) hoặc sử dụng lớp hàm khác (có nhiều lựa chọn thay thế cho 'std :: function' trong tự nhiên, hoặc bạn có thể thiết kế riêng của bạn). –

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