Итак, будем назначать различные указатели на экземпляры классов и методы-члены, как показано в Листинг 13.
Листинг 13. Настройка указателей на классы и методы
int main()
{
Initiator initiator;
Executor executor;
Executor1 executor1;
Executor2 executor2;
Executor3 executor3;
initiator.setup(&executor, &Executor::callbackHandler1); // (1)
initiator.setup(&executor, &Executor::callbackHandler2); // (2)
initiator.setup(&executor1, &Executor::callbackHandler1); // (3)
initiator.setup(&executor1, &Executor::callbackHandler2); // (4)
initiator.setup(&executor2, &Executor::callbackHandler1); // (5)
initiator.setup(&executor2, &Executor::callbackHandler2); // (6)
//initiator.setup(&executor3, &Executor::callbackHandler1); //Incorrect, base class is ambiguous // (7)
//initiator.setup(&executor3, &Executor::callbackHandler2); //Incorrect, base class is ambiguous // (8)
initiator.setup((Executor1*)&executor3, &Executor::callbackHandler1); // (9)
initiator.setup((Executor1*)&executor3, &Executor::callbackHandler2); // (10)
initiator.setup((Executor2*)&executor3, &Executor::callbackHandler1); // (11)
initiator.setup((Executor2*)&executor3, &Executor::callbackHandler2); // (12)
}
В строках 1 и 2 все прозрачно: какой метод назначен, такой и будет вызван.
В строке 3 мы назначаем указатель на метод Executor::callbackHandler1, но поскольку в классе Executor1он переопределен, будет вызван метод Executor1::callbackHandler1.
В строке 4 мы назначаем указатель на Executor::callbackHandler2; в классе Executor1такого метода нет (т.е. он не переопределен), поэтому будет вызван метод базового класса Executor::callbackHandler2.
В строке 5 мы назначаем указатель на Executor::callbackHandler1; в классе Executor2метод не переопределен, поэтому будет вызван метод базового класса Executor::callbackHandler2.
В строке 6 мы назначаем указатель на Executor::callbackHandler2; в классе Executor2он переопределен, поэтому будет вызван метод Executor2:: callbackHandler2.
С классом Executor3ситуация еще интереснее, поскольку он использует множественное наследование 6 6 Вообще, множественное наследование – неоднозначный механизм, который часто подвергается критике. В большинстве современных языков (например, Java, C#, Ruby и др.) множественное наследование не поддерживается. Тем не менее, в C++ множественное наследование существует, поэтому необходимо рассмотреть и такой случай.
. Мы не можем напрямую назначать указатели на методы базового класса, как это приведено в строках 7 и 8, потому что если взглянуть на иерархию наследования, то можно увидеть, что к базовому классу можно добраться двумя путями – через Executor1либо через Executor2. Таким образом, компилятор не знает, по какому пути выполнять поиск методов, и выдает ошибку. По указанной причине мы должны явно указать в цепочке наследования класс-предшественник. Если в пути наследования какая-нибудь функция окажется переопределена, то она будет вызвана, в противном случае будет вызвана функция базового класса.
В строке 9 мы в качестве предшественника указываем класс Executor1и назначаем указатель на метод callbackHandler1. В Executor1этот метод переопределен, и он будет вызван. В строке 10 мы назначаем указатель на метод callbackHandler2; в Executor1этот метод не переопределен, поэтому будет вызван метод базового класса Executor::callbackHandler2. Если мы в качестве предшественника будем указывать Executor2, как это показано в строках 11 и 12, то получится все наоборот: в строке 11 будет вызван метод базового класса Executor:: callbackHandler1, а в строке 12 будет вызван соответствующий переопределенный метод Executor2::callbackHandler2.
Для наглядности сведем результаты в Табл. 3.
Табл. 3. Вызовы методов по цепочке наследования
Используя рассмотренные способы управления контекстом, можно реализовать довольно изощренную логику обработки и динамически ее изменять в процессе выполнения программы.
Реализация инициатора для синхронного вызова представлена в Листинг 14. В отличие от асинхронного вызова, здесь аргументы не хранятся, а передаются как входные параметры функции.
Листинг 14. Инициатор для синхронного обратного вызова с указателем на метод-член класса
class Executor;
using ptr_method_callback_t = void(Executor::*)(int);
void run(Executor* ptrClientCallbackClass, ptr_method_callback_t ptrClientCallbackMethod)
{
int eventID = 0;
//Some actions
(ptrClientCallbackClass->*ptrClientCallbackMethod)(eventID);
}
2.3.6. Преимущества и недостатки
Читать дальше