2013-10-01 36 views
6

Ai đó có thể mô tả lý do tại sao mã này không hoạt động (trên GCC4.7.3 seg-lỗi trước khi trở về từ cuộc gọi)?bộ nhớ quản lý cho lambda trong C++ 11

#include <iostream> 
#include <functional> 
#include <memory> 

using namespace std; 

template<typename F> 
auto memo(const F &x) -> std::function<decltype(x())()> { 
    typedef decltype(x()) return_type; 
    typedef std::function<return_type()> thunk_type; 
    std::shared_ptr<thunk_type> thunk_ptr = std::make_shared<thunk_type>(); 

    *thunk_ptr = [thunk_ptr, &x]() { 
     cerr << "First " << thunk_ptr.get() << endl; 
     auto val = x(); 
     *thunk_ptr = [val]() { return val; }; 
     return (*thunk_ptr)(); 
    }; 

    return [thunk_ptr]() { return (*thunk_ptr)(); }; 
}; 

int foo() { 
    cerr << "Hi" << endl; 
    return 42; 
} 

int main() { 
    auto x = memo(foo); 
    cout << x() << endl ; 
    cout << x() << endl ; 
    cout << x() << endl ; 
}; 

giả định ban đầu của tôi:

    mỗi
  1. std::function<T()> là kinda tham khảo/shared_ptr đối với một số đối tượng đại diện cho đóng cửa. I E. thời gian sống của giá trị được chọn bị giới hạn bởi nó.
  2. std::function<T()> đối tượng có toán tử gán sẽ từ bỏ đóng cửa cũ (giá trị được chọn vào thời gian kết thúc) và sẽ sở hữu một giá trị mới.

P.S. Câu hỏi này đưa ra sau khi tôi đọc question about lazy in C++11

+0

Hm, bằng cách nào đó 'thunk_ptr' kết thúc sở hữu riêng của mình. Điều đó có vẻ không đúng. –

+0

@KerrekSB, có, đồng ý, nhưng điều này sẽ bị rò rỉ bộ nhớ chứ không phải là lỗi – ony

+0

Lý do đằng sau 'return (* thunk_ptr)();' là trả về tham chiếu đến trường đóng '[val]() {return val; } 'nếu tham chiếu được thêm vào' thunk_type'. Xem (thực hiện thay thế 'lười biếng' trong câu trả lời của tôi) [http://stackoverflow.com/a/19125422/230744] – ony

Trả lời

5

Đây là mã có vấn đề:

[thunk_ptr, &x]() { 
    auto val = x(); 
    *thunk_ptr = [val]() { return val; }; 
    return (*thunk_ptr)(); // <--- references a non-existant local variable 
} 

Vấn đề là các địa phương thunk_ptr là một bản sao từ bối cảnh. Tức là, trong bài tập *thunk_ptr = ..., thunk_ptr là bản sao thuộc sở hữu của đối tượng hàm. Tuy nhiên, với nhiệm vụ, đối tượng hàm không còn tồn tại nữa. Tức là, trên dòng tiếp theo thunk_ptr đề cập đến một đối tượng vừa bị phá hủy.

Có một vài phương pháp để khắc phục vấn đề:

  1. Thay vì nhận được ưa thích, chỉ cần trở val. Vấn đề ở đây là return_type có thể là một kiểu tham chiếu sẽ khiến phương pháp này thất bại.
  2. Return kết quả trực tiếp từ việc chuyển nhượng: trước khi chuyển nhượng thunk_ptr vẫn còn sống và sau khi chuyển nhượng nó vẫn trả về một tham chiếu đến đối tượng std::function<...>():

    return (*thunk_ptr = [val](){ return val; })(); 
    
  3. Safe một bản sao của thunk_ptr và sử dụng này bản sao để gọi các đối tượng chức năng trong tuyên bố return:

    std::shared_ptr<thunk_type> tmp = thunk_ptr; 
    *tmp = [val]() { return val; }; 
    return (*tmp)(); 
    
  4. Lưu một bản sao của tài liệu tham khảo để std::function và sử dụng nó inst ead đề cập đến lĩnh vực thuộc về ghi đè đóng cửa:

    auto &thunk = *thunk_ptr; 
    thunk = [val]() { return val; }; 
    return thunk(); 
    
+0

Toàn bộ ý tưởng là có tính toán lười biếng của' val'. Đó là lý do tại sao có hai "sự tiếp tục" (đánh giá và trả về giá trị được đánh giá). Xin lưu ý rằng tôi phân bổ 'std :: function ' trên heap với 'std :: make_shared' và sau đó tôi sao chép một chia sẻ-ptr để đóng cửa đầu tiên (increment ref) và sau đó tôi sao chép chia sẻ-ptr lần nữa trong lần đóng thứ hai (trong tổng số +2 refs) và sau khi thoát khỏi hàm tôi thả một ref. Trong tổng số 2 refs: một được tổ chức bởi lần đầu tiên đóng cửa (ref tròn như @KerrekSB đã đề cập) và một tổ chức bằng cách đóng cửa trở lại (có thể được giải phóng và giảm xuống 1 ref).Tôi không thể nhìn thấy những gì bạn nói. – ony

+0

@ony: Vấn đề là ** không ** với đối tượng 'std :: function <...>' nhưng với bản sao cục bộ của 'std :: shared_ptr <...>' trong lambda thay thế: đối tượng 'std :: finction <...>' là chủ sở hữu duy nhất của lambda này. Khi bạn gán cho 'std :: function <...>' đối tượng lambda chúng ta đã phá hủy và với nó là 'std :: shared_ptr <...>' ('thunk_ptr'). Bạn cần giữ một bản sao của 'thunk_ptr' khi bạn muốn truy cập đối tượng' std :: function <...> 'khi' thunk_ptr' bị hủy (việc gán cho hàm 'std :: function <...>' hoạt động giống như 'xóa this'). ... và tôi hiểu ý định của việc này! –

+0

Có, bạn đang đúng 'thunk_ptr' bên trong đóng cửa đề cập đến địa chỉ của trường bên trong đối tượng đóng cửa đã bị phá hủy bởi nhiệm vụ đó với đóng cửa mới. Xin lỗi, tôi đã giải thích sai câu trả lời của bạn. Tôi sẽ chờ đợi khi SO sẽ cho phép tôi bỏ phiếu ở đây một lần nữa. Bạn có thể chỉnh sửa câu trả lời của bạn chỉ để cho tôi hoàn tác ý kiến ​​của tôi. – ony