2011-12-14 31 views
6

Tôi đang tăng cường học tập :: asio và C++ 11 cùng một lúc. Một trong những chương trình thử nghiệm của tôi, đó thực sự là một sự thích nghi của one of the samples given in the boost::asio tutorial như sau:Làm thế nào để viết một hàm ẩn danh/lambda tự chuyển nó thành một cuộc gọi lại?

#include <iostream> 
#include <boost/asio.hpp> 
#include <boost/date_time/posix_time/posix_time.hpp> 

class printer { 

// Static data members 
private: 
    const static boost::posix_time::seconds one_second; 

// Instance data members 
private: 
    boost::asio::deadline_timer timer; 
    int count; 

// Public members 
public: 
    printer(boost::asio::io_service& io) 
     : timer(io, one_second), count(0) { 

     std::function<void(const boost::system::error_code&)> callback; 
     callback = [&](const boost::system::error_code&) { // critical line 
      if (count < 5) { 
       std::cout << "Current count is " << count++ << std::endl; 

       timer.expires_at(timer.expires_at() + one_second); 
       timer.async_wait(callback); 
      } 
     }; 

     timer.async_wait(callback); 
    } 

    ~printer() { 
     std::cout << "Final count is " << count << std::endl; 
    } 
}; 

const boost::posix_time::seconds printer::one_second(1); 

int main() { 
    boost::asio::io_service io; 
    printer p(io); 
    io.run(); 

    return 0; 
} 

Khi tôi chạy chương trình này, tôi nhận được một lỗi segmentation. Tôi hiểu tại sao tôi bị lỗi phân đoạn. Sau khi hàm tạo được thực hiện xong, biến số callback của hàm khởi tạo nằm ngoài phạm vi và biến số callback của lambda, là tham chiếu đến biến số callback của hàm tạo, trở thành tham chiếu đáng lơ lửng.

Vì vậy, tôi sửa đổi dòng quan trọng với:

 callback = [callback, &](const boost::system::error_code&) { // critical line 

Sau đó biên dịch nó, chạy nó, và nhận được một lỗi chức năng gọi xấu. Một lần nữa, tôi hiểu tại sao tôi nhận được lỗi cuộc gọi chức năng xấu. Trong phạm vi lambda, biến số callback của hàm tạo vẫn chưa được gán bất kỳ giá trị nào, vì vậy nó cho tất cả các mục đích thực tế là một con trỏ hàm lơ lửng. Do đó, biến số callback của lambda, là một bản sao của biến số callback của hàm tạo, cũng là một con trỏ hàm lơ lửng.


Sau khi suy nghĩ về vấn đề này trong một thời gian, tôi nhận ra rằng những gì tôi thực sự cần là gọi lại có thể tham khảo để tự sử dụng một con trỏ hàm, không phải là một tham chiếu đến một con trỏ hàm. Các mẫu đạt được điều này bằng cách sử dụng một chức năng được đặt tên như gọi lại, thay vì một ẩn danh. Tuy nhiên, việc chuyển các hàm có tên gọi là callbacks không phải là rất thanh lịch. Có cách nào để có được một chức năng nặc danh có một con trỏ chức năng cho chính nó như là một biến địa phương?

Trả lời

0

Lambda đang ghi tham chiếu biến cục bộ "gọi lại", khi lambda chạy sẽ không hợp lệ.

+1

Tôi đã nói rõ ràng rằng tôi hiểu tại sao chương trình của tôi không hoạt động. Tôi muốn biết _how_ một lambda có thể có một con trỏ hàm (chính nó kéo dài mãi mãi) thay vì tham chiếu đến một con trỏ hàm tới chính nó (có thể trở thành tham chiếu lơ lửng khi biến con trỏ hàm thực tế không nằm trong phạm vi). – pyon

+0

OK, tôi nghĩ rằng giải pháp đã phần nào rõ ràng khi tước nó xuống nguyên tắc tại sao nó không hoạt động. Bạn đã thử làm "callback" một thành viên của lớp? –

8

Có một số lựa chọn thay thế:

  • Dừng sử dụng một lambda. Bạn không phải sử dụng chúng cho mọi thứ, bạn biết đấy. Chúng bao gồm rất nhiều trường hợp, nhưng chúng không bao gồm mọi thứ. Chỉ cần sử dụng một functor cũ thường xuyên.
  • Đặt cửa hàng lambda một con trỏ thông minh đến một số được phân bổ động std::function lưu trữ lambda. Ví dụ:

    auto pCallback = std::make_shared<std::function<void(const boost::system::error_code&)>>(); 
    auto callback = [=](const boost::system::error_code&) { // critical line 
        if (count < 5) { 
         std::cout << "Current count is " << count++ << std::endl; 
    
         timer.expires_at(timer.expires_at() + one_second); 
         timer.async_wait(pCallback.get()); 
        } 
    }; 
    *pCallback = callback; 
    
+0

Sử dụng hàm functor sẽ giống hệt như sử dụng hàm được đặt tên. Tên của functor sẽ là tên thật của hàm. // Bên cạnh đó, nếu tôi sẽ sử dụng một hàm functor, thì tại sao không làm cho lớp 'printer' là functor? – pyon

+1

@ EduardoLeón: Như tôi đã nói, Lambdas không dành cho * mọi thứ *. Đôi khi, bạn chấp nhận những giới hạn mà bạn có và làm việc bên trong chúng. Hoặc bạn chỉ có thể làm điều khác mà tôi đã nói. Cá nhân, thực tế là bạn sẽ phải sử dụng một phương pháp con trỏ như thế này mạnh mẽ cho thấy rằng bạn có lẽ nên sử dụng phương pháp functor rõ ràng hơn và rõ ràng hơn. Ít nhất với điều đó, nó rõ ràng những gì đang xảy ra. –

3

Để tìm hiểu về Asio và C++ 11 Tôi khuyên bạn nên nói chuyện boostcon "Tại sao C++ 0x là Awesomest ngôn ngữ cho Mạng Lập trình" của nhà thiết kế của ASIO mình. (Christopher Kohlhoff)

https://blip.tv/boostcon/why-c-0x-is-the-awesomest-language-for-network-programming-5368225 http://github.com/chriskohlhoff/awesome

Trong bài nói chuyện này, C.K mất một ứng dụng ASIO nhỏ điển hình và bắt đầu để thêm C++ 11 tính năng từng cái một. Có một phần về lambda ở giữa cuộc nói chuyện. Các loại vấn đề mà bạn có với cuộc sống thời gian của lambda là cách giải quyết bằng cách sử dụng các mô hình sau:

#include <iostream> 
#include <boost/asio.hpp> 
#include <boost/date_time/posix_time/posix_time.hpp> 
#include <memory> 

class printer 
{ 

// Static data members 
private: 
    const static boost::posix_time::seconds one_second; 

// Instance data members 
private: 
    boost::asio::deadline_timer timer; 
    int count; 

// Public members 
public: 
    printer(boost::asio::io_service& io) 
     : timer(io, one_second), count(0) { 
     wait(); 
    } 

    void wait() { 
     timer.async_wait(
      [&](const boost::system::error_code& ec) { 
       if (!ec && count < 5) { 
       std::cout << "Current count is " << count++ << std::endl; 

       timer.expires_at(timer.expires_at() + one_second); 
       wait(); 
       } 
      }); 
    } 

    ~printer() { 
     std::cout << "Final count is " << count << std::endl; 
    } 
}; 

const boost::posix_time::seconds printer::one_second(1); 

int main() { 
    boost::asio::io_service io; 
    printer p(io); 
    io.run(); 

    return 0; 
} 
+0

Nó không trả lời một cách kỹ thuật câu hỏi, nhưng nó giải quyết được vấn đề. +1. –

1

Chỉ lý thuyết: bạn có thể làm những việc như vậy với những gì được gọi là combinators (như I, S, K).

Trước khi sử dụng biểu thức lambda ẩn danh e loại F, trước tiên bạn có thể xác định các chức năng như doubleF; (F) -> (F, F) hoặc applyToOneself: (F f) -> F = {return f (f); }.

1

Hiện tại, có đề xuất cho add a Y-combinator to the C++ standard library (P0200R0) để giải quyết chính xác vấn đề này.

Ý tưởng cơ bản ở đây là bạn truyền lambda cho chính nó như là một đối số đầu tiên. Một lớp trợ giúp vô hình sẽ chăm sóc lời gọi đệ quy trong nền bằng cách lưu trữ lambda trong một thành viên có tên.

Việc thực hiện mẫu từ đề xuất như sau:

#include <functional> 
#include <utility> 

namespace std { 

template<class Fun> 
class y_combinator_result { 
    Fun fun_; 
public: 
    template<class T> 
    explicit y_combinator_result(T &&fun): fun_(std::forward<T>(fun)) {} 

    template<class ...Args> 
    decltype(auto) operator()(Args &&...args) { 
     return fun_(std::ref(*this), std::forward<Args>(args)...); 
    } 
}; 

template<class Fun> 
decltype(auto) y_combinator(Fun &&fun) { 
    return y_combinator_result<std::decay_t<Fun>>(std::forward<Fun>(fun)); 
} 

} // namespace std 

Mà có thể được sử dụng như sau để giải quyết vấn đề từ câu hỏi:

timer.async_wait(std::y_combinator([](auto self, const boost::system::error_code&) { 
    if (count < 5) { 
     std::cout << "Current count is " << count++ << std::endl; 

     timer.expires_at(timer.expires_at() + one_second); 
     timer.async_wait(self); 
    } 
})); 

Lưu ý self lập luận cho rằng đã được thông qua vào lambda. Điều này sẽ bị ràng buộc bởi kết quả của cuộc gọi y_combinator, là một đối tượng hàm tương đương với lambda với đối số self đã bị ràng buộc (nghĩa là chữ ký của nó là void(const boost::system::error_code&)).

+0

Hữu ích, nhưng thực sự xấu xí. Sửa chữa các điểm cố định bằng tay không phải là một điều. – pyon

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