(Мой предыдущий вопрос имеет аналогичную формулировку и примеры, но задает совсем другое. Раньше я спрашивал об идеях для подходов. Теперь я спрашиваю, как получить конкретный подход к работе.)
У меня есть функция подписки, которая будет вызывать мой обратный вызов, когда что-то произойдет. (Допустим, это таймер, и он передаст мне объект, когда истечет определенное количество миллисекунд.) Посмотрев на лямбда-выражения, 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
. С другой стороны, дополнительная сложность в суперклассе не является проблемой, поскольку это однократно записываемый код.