2014년 12월 27일 토요일

boost::asio timer 사용하기

오늘은 boost::asio 예제코드를 보다가 (몰라서) 헤멧던 내용을 공유할까 합니다.
c++ 이나 boost 같은 라이브러리에 익숙하지 않은 저 같은 c++ 초보자들에게 도움이 되길 바라며 :-)
오늘은 그냥… 괜히… Windows 의 타이머 객체를 사용하고 싶지 않아서, boost::asio 의 timer 예제코드 를 조금 수정해서 아래와 같은 코드를 작성했습니다. 간단히 설명하자면 정해진 시간(1초)이 지나면 callback 함수(print(…))를 5번만 호출하는 코드입니다.
void print(const boost::system::error_code& e, boost::asio::steady_timer* timer, int* count)
{
    if (*count < 5)
    {
        std::cout << *count << "\n";
        ++(*count);

        timer->expires_from_now(boost::chrono::seconds(1));
        timer->async_wait(boost::bind(print, boost::asio::placeholders::error, timer, count));
    }
}


int main()
{
    boost::asio::io_service io_service;

    int count = 0;
    boost::asio::steady_timer timer(io_service);
    timer.async_wait(boost::bind(
                                print,
                                boost::asio::placeholders::error,
                                &timer,
                                &count));
    io_service.run();

    std::cout << "count = " << count << std::endl;
    return true;
}
timer.async_wait() 함수는 아래와 같은 함수 포인터 또는 함수객체를 파라미터로 받습니다. 여기에 자세한 내용이 있습니다.
void handler(
  const boost::system::error_code& error // Result of operation.
);
예제에서는 부가적인 파라미터를 전달 받는 print() 함수를 async_wait() 의 파라미터로 사용하기 위해서 아래의 코드 처럼 boost::bind() 를 이용했습니다.
    timer.async_wait(boost::bind(
                                print,
                                boost::asio::placeholders::error,
                                &timer,
                                &count));
저는 print() 함수에 전달되는 파라미터로 boost::asio::deadline_timer 와 count 의 포인터 타입이 사용되는것이 그냥 맘에 안들어서 참조자로 바꾸고 싶었습니다.
boost::asio::placeholders::error 는 boost::bind() 를 사용할 때 _1 같은 역할을 하는 placeholder argument 이므로, boost::bind(print, _1, timer, count) 와 같으므로, timer 와 count 를 참조자로 넘겨도 아무 문제 없을 것 같았습니다.
void
print(
    const boost::system::error_code& /*e*/,
    boost::asio::deadline_timer& t,
    int& count
    )
print() 함수 파라미터를 참조자를 받도록 변경하고 컴파일하면 아래처럼 복잡한 에러가 발생합니다.
1>  All outputs are up-to-date.
1>  _test_boost_asio_timer.cpp
1>c:\Boost_x64\include\boost-1_56\boost/asio/basic_waitable_timer.hpp(514): error C2248: 'boost::asio::basic_io_object<IoObjectService>::basic_io_object' : cannot access private member declared in class 'boost::asio::basic_io_object<IoObjectService>'
1>          with
1>          [
1>              IoObjectService=boost::asio::waitable_timer_service<boost::chrono::steady_clock,boost::asio::wait_traits<boost::chrono::steady_clock>>
1>          ]
1>          c:\Boost_x64\include\boost-1_56\boost/asio/basic_io_object.hpp(163) : see declaration of 'boost::asio::basic_io_object<IoObjectService>::basic_io_object'
1>          with
1>          [
1>              IoObjectService=boost::asio::waitable_timer_service<boost::chrono::steady_clock,boost::asio::wait_traits<boost::chrono::steady_clock>>
1>          ]
1>          This diagnostic occurred in the compiler generated function 'boost::asio::basic_waitable_timer<Clock>::basic_waitable_timer(const boost::asio::basic_waitable_timer<Clock> &)'
1>          with
1>          [
1>              Clock=boost::chrono::steady_clock
1>          ]
1>
1>Build FAILED.
찬찬히 읽어보니 boost::asio::basic_io_object<IoObjectService> 클래스에 정의된 private member 에 접근하지 못한다는 에러입니다. 대체 이런 에러는 어디서 나는걸까 고민하다가 boost::bind 문서를 다시 한번 읽어보니 해답이 보이더군요.
a copy of the value of i is stored into the function object. boost::ref and boost::cref can be used to make the function object store a reference to an object, rather than a copy:
boost::bind 는 고정된 변수값을 call by value 로 넘기는게 기본인가 봅니다. (미루어 보건데 - 소스를 보지 않았으니 - boost::bind 는 함수 객체를 생성하는것으로 구현했는가 보네요.)
결국 boost::bind 호출시 사용된 timer 객체가 call by value 로 넘어가면서 복사 생성자나 대입 연산자가 호출되는 시점에 오류가 발생한 것 같습니다.
/// Base class for all I/O objects.
/**
 * @note All I/O objects are non-copyable. However, when using C++0x, certain
 * I/O objects do support move construction and move assignment.
 */
#if !defined(BOOST_ASIO_HAS_MOVE) || defined(GENERATING_DOCUMENTATION)
template <typename IoObjectService>
#else
template <typename IoObjectService,
    bool Movable = detail::service_has_move<IoObjectService>::value>
#endif
class basic_io_object
{
public:
...
protected:
  /// Construct a basic_io_object.
  /**
   * Performs:
   * @code get_service().construct(get_implementation()); @endcode
   */
  explicit basic_io_object(boost::asio::io_service& io_service)
    : service(boost::asio::use_service<IoObjectService>(io_service))
  {
    service.construct(implementation);
  }

private:
  basic_io_object(const basic_io_object&);
  basic_io_object& operator=(const basic_io_object&);
};
코드를 따라가 보니 class basic_waitable_timer 템플릿 클래스는 public basic_io_object 를 상속받았고, basic_io_object 템플릿 클래스의 복사 생성자와 대입연산자가 private 으로 선언되어있음을 확인 할 수 있었습니다. 클래스 자체가 복사하지 않도록 디자인된 녀석이었습니다!
포인터를 사용하지 않고, 참조자를 사용하도록 수정한 코드는 아래와 같습니다.
void print(const boost::system::error_code& e, boost::asio::steady_timer& timer, int& count)
{
    if (count < 5)
    {
        std::cout << count << "\n";
        ++(count);

        timer.expires_from_now(boost::chrono::seconds(1));
        timer.async_wait(boost::bind(print, boost::asio::placeholders::error, boost::ref(timer), boost::ref(count)));
    }
}

bool test_boost_asio_timer_3()
{
    boost::asio::io_service io_service;

    int count = 0;
    boost::asio::steady_timer timer(io_service);
    timer.async_wait(boost::bind(
                                print,
                                boost::asio::placeholders::error,
                                boost::ref(timer),
                                boost::ref(count)));
    io_service.run();

    std::cout << "count = " << count << std::endl;
    return true;
}
오늘도 하나 배웠습니다. :-)

댓글 없음:

댓글 쓰기