среда, 21 апреля 2010 г.

[prog.c++] Пример использования указателей на методы в C++

Пару дней назад на работе зашел разговор о том, как можно использовать указатели на методы в C++. Штука это специфическая и применяется мной не часто, реже чем указатели на функции. Да еще, как обычно, когда нужно вспомнить яркий пример чего-нибудь, память не подсказывает ничего путного. Поэтому не смог я тогда вспомнить хорошего и убедительного примера.

А сейчас расширяю функциональность одного старого агента, в котором указатели на методы используются для замеров времен выполнения операций. Агент работает потактово, на каждом такте выполняется серия операций. Нужно мониторить максимальное и среднее времена работы каждой из них. В коде это выглядит так:

void
a_cp_service_t::do_all_periodic_actions()
   {
      measure_duration(
            m_monitors.m_avg_time_save_outgoing_pkg,
            this,
            &a_cp_service_t::handle_received_outgoing_packages );

      measure_duration(
            m_monitors.m_avg_time_handle_SR,
            this,
            &a_cp_service_t::handle_received_send_results );

      measure_duration(
            m_monitors.m_avg_time_handle_SR_pkg_ack,
            this,
            &a_cp_service_t::handle_received_SR_package_acks );

Где measure_duration – это очень простая функция-шаблон:

template< class T >
inline void
measure_duration(
   monitor_t & monitor,
   T * object,
   void (T::*method)() )
   {
      ACE_Time_Value s = ACE_OS::gettimeofday();
      (object->*method)();
      ACE_Time_Value f = ACE_OS::gettimeofday();

      monitor.add_time_slice( f - s );
   }

Т.е. указатели на методы в C++ позволили обойтись без определения каких-либо интерфейсов (к чему пришлось бы прибегнуть в Java), либо лямбда-функций-делегатов (C#/D/Scala), либо рефлексии (опять же Java, если интерфейсы не нравятся), либо макросов (если не знать, что такое указатели на методы в C++). А так все просто, прозрачно и эффективно.

8 комментариев:

  1. класическим примером является случай, когда нужно передать указатель на мембер в сишный коллбэк (win api или др. библиотека)

    ОтветитьУдалить
  2. Насколько я помню, так можно сделать только для статического метода. Указатель на нестатический метод класса, обычно, даже больше, чем указатель на функцию. Поэтому его даже привести к какому-нибудь типу коллбэк-функции нельзя безопасно.

    ОтветитьУдалить
  3. "этот стон у нас песней зовется" (с)сами знаете :)
    технически код полностью эквивалентен
    void a_cp_service_t::do_all_periodic_actions()
    {
    { scope_timer_t t(m_monitors.m_avg_time_save_outgoing_pkg);
    this->handle_received_outgoing_packages();
    }
    ... и так 3 раза

    Ну и в посте осознанно смешивается измерение "методов" фасада подсистемы с помощью сисколла и измерение любого метода под соусом "эффективно". Джава пробивает ? ;)
    Легкий pun intended xD
    ps код форматировать в блоггере классно - даже pre не понимает :)

    ОтветитьУдалить
  4. сорри за форматироване, исходники редко вставляю

    template< typename T, void (T::*name)() >
    void test ( void * ptr ) {
    (static_cast<T *>(ptr)->*name)();
    }


    struct Foo {
    void x () { std::cout << __func__ << std::endl; }
    void y () { std::cout << __func__ << std::endl; }
    };

    int main()
    {
    Foo x;
    void (*v)( void * );
    v = test<Foo,&Foo::x>;
    v(&x);
    v = test<Foo,&Foo::y>;
    v(&x);
    return 0;
    }

    ОтветитьУдалить
  5. 2Rubanets Myroslav:

    >технически код полностью эквивалентен

    Почти. Но с вызовом measure_duration намерения разработчика, имхо, более четко выражены.

    Тонкое различие в двух подходах: деструктор scoped_timer_t будет отрабатывать всегда, даже если в методе выскочило исключение.

    >Ну и в посте осознанно смешивается измерение "методов" фасада подсистемы с помощью сисколла и измерение любого метода под соусом "эффективно"

    Под эффективно я понимал здесь то, что никаких дополнительных ресурсов тратиться не будет. Системные вызовы по-любому нужно будет делать. Но если в Java создавать анонимные классы, реализующие интерфейс, или делегаты в D (блоки кода в Ruby), то тут еще будет лишний объект на хипе создаваться. Но это все мелочи по сравнению с тем, как некоторые Java-программисты в подобных случаях рефлексию используют.

    >код форматировать в блоггере классно - даже pre не понимает

    Угу, знал бы я об этом в самом начале...

    ОтветитьУдалить
  6. 2night beast:

    Вау! Классно! Я такими фокусами никогда не пользовался. Нужно будет запомнить.

    ОтветитьУдалить
  7. 2Евгений
    при исключении время система разумеется не потребляет и мерять его не надо ;)

    ну и должен сказать спасибо за пост - комментарии доставляют.

    ОтветитьУдалить
  8. >при исключении время система разумеется не потребляет и мерять его не надо

    it depends. Иногда и не нужно мерять время сбойных операций, поскольку оно будет на несколько порядков отличаться от нормальных времен.

    Поэтому лучше детали упрятать в measure_duration, который может быть реализован как через scoped_timer_t, так и через прямые обращения к gettimeofday.

    >ну и должен сказать спасибо за пост - комментарии доставляют.

    Ну дык -- завсегда пожалуйста, заходите еще :)

    ОтветитьУдалить