обратные вызовы указателя на метод в С++ 11/14/17?

(Мой предыдущий вопрос имеет аналогичную формулировку и примеры, но задает совсем другое. Раньше я спрашивал об идеях для подходов. Теперь я спрашиваю, как получить конкретный подход к работе.)

У меня есть функция подписки, которая будет вызывать мой обратный вызов, когда что-то произойдет. (Допустим, это таймер, и он передаст мне объект, когда истечет определенное количество миллисекунд.) Посмотрев на лямбда-выражения, std:function и std:bind, я думаю, что решение с указателями на методы более производительно и проще в написании (особенно для подписчик), но я не могу понять последний бит.

Этот пример немного отражает мой проект: у нас есть фреймворк, представленный Foo, который написан один раз, и у нас будет много подклассов, представленных здесь Bar, которые будут написаны людьми с большим знанием предметной области, но меньшим знанием C++. Итак, мы хотим, чтобы этот вызов SubscribeTimer() был максимально простым. Наконец, приложение является высокопроизводительным, и мы хотели бы исключить использование кучи, включая создание неявных объектов std::bind и т.д.

#include <iostream>
#include <functional>
using namespace std;

class Data { int i; };

class Foo {
public:
    typedef void (Foo::*Timer_T)( Data* pd );
    virtual void SubscribeTimer( int iMilliseconds, Timer_T pmethod );
    virtual void SubscribeTimer( int iMilliseconds, std::function<void(Data*)> pfn ); // undesired

    virtual void OnTimerA( Data* pd ) { cout << "Foo::OnTimerA called" << endl; };
};

void Foo::SubscribeTimer( int iMilliseconds, Timer_T pmethod ) {
    Data d;
    (this->*pmethod)( &d );
}

void Foo::SubscribeTimer( int iMilliseconds, std::function<void(Data*)> pfn ) { // undesired
    Data d;
    pfn( &d );
}

class Bar: public Foo {
    public:
    void Init();
    virtual void OnTimerA( Data* pd ) { cout << "Bar::OnTimerA called" << endl; };
    virtual void OnTimerB( Data* pd ) { cout << "Bar::OnTimerB called" << endl; };
};

void Bar::Init() {
    // Works like I want it to: easy to subscribe, and high performance.
    SubscribeTimer( 1000, &Foo::OnTimerA );
    // What I'd like to do, but doesn't work.
    //SubscribeTimer( 1000, &Bar::OnTimerB );
    // Does exactly what I want except more complicated to write and I believe slower to run.
    SubscribeTimer( 1000, std::bind( &Bar::OnTimerB, this, std::placeholders::_1 ) );
}


int main( int nArg, const char* apszArg[] ) {
    Bar bar;
    bar.Init();
}

Как и ожидалось (если вы пропустите требование писать Foo::, а не Bar:: в вызове Init() к SubscribeTimer()), программа выводит:

Bar::OnTimerA called  (from the version of SubscribeTimer() I like)
Bar::OnTimerB called  (from the version of SubscribeTimer() I think is too verbose/slow)

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

Итак, в предложении: как я могу передать OnTimerB() в версию указателя метода SubscribeTimer()? Я с радостью изменю определение Timer_T, SubscribeTimer() или что-то еще. Тем не менее, нет смысла в решении с указателем на член, более сложном для подкласса, чем реализация std::function, и нет смысла в решении, более медленном, чем реализация std::function. С другой стороны, дополнительная сложность в суперклассе не является проблемой, поскольку это однократно записываемый код.


person Swiss Frank    schedule 26.11.2019    source источник


Ответы (1)


Чтобы вызвать Bar::OnTimerB, вам нужен указатель типа Bar. Поэтому вам необходимо реализовать какое-то стирание типа.

class Foo {
public:
  template <typename Derived>
  void SubscribeTimer(int iMilliseconds, void (Derived::*pmethod)(Data* pd)) {
    auto const self = static_cast<Derived*>(this);
    Data d;
    (self->*pmethod)(&d);
  }
};

Очевидно, что если вы собираетесь вызвать обратный вызов позже, вам нужно как-то это сохранить. Это то, что std::function<> абстрагирует для вас. Вы могли бы, конечно, сделать это сами, но std::function<> не так уж и плохо, особенно на платформах с оптимизацией мелких функций.

Поэтому вы можете сделать внутреннюю оболочку std::function:

  template <typename Derived>
  void SubscribeTimer(int iMilliseconds, void (Derived::*pmethod)(Data* pd)) {
    SubscribeTimer(iMilliseconds,
        [self = static_cast<Derived*>(this), pmethod](Data* pd) {
            (self->*pmethod)(pd);
        });
  }
person Anthony Williams    schedule 26.11.2019