Виталий Ткаченко - Обратные вызовы в C++
- Название:Обратные вызовы в C++
- Автор:
- Жанр:
- Издательство:Array SelfPub.ru
- Год:2021
- ISBN:нет данных
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Виталий Ткаченко - Обратные вызовы в C++ краткое содержание
Обратные вызовы в C++ - читать онлайн бесплатно полную версию (весь текст целиком)
Интервал:
Закладка:
4.6.4. Исполнитель
Реализация исполнителя для инициатора с универсальным аргументом (см. Листинг 53 п. 4.6.2) приведена в Листинг 56, здесь используется CallbackConverterиз Листинг 54 п. 4.6.3.
class Executor
{
public:
static void staticCallbackHandler(Executor* executor, int eventID) {}
void callbackHandler(int eventID) {}
void operator() (int eventID) {}
};
void ExternalHandler(void* somePointer, int eventID) {}
int main()
{
int capturedValue = 0;
Initiator initiator;
Executor executor;
// Pointer to the external function
initiator.setup(CallbackConverter(ExternalHandler, &executor));
// Pointer to the static method
initiator.setup(CallbackConverter(Executor::staticCallbackHandler, &executor));
// Pointer to the class member method
initiator.setup(CallbackConverter(&Executor::callbackHandler, &executor));
// Functional object
initiator.setup(executor);
// Lambda-expression
initiator.setup([capturedValue](int eventID) {});
}
Если сравнить приведенную реализацию исполнителя для шаблона-инициатора с фиксированным типом аргумента (Листинг 43 и Листинг 44 п. 4.4.3) с приведенной, то можно заметить следующее. В первом случае для каждого типа аргумента приходится объявлять отдельный инициатор, инстанциируя его соответствующим типом. Здесь инициатор объявляется один раз, после чего тип аргумента вызова настраивается в процессе выполнения программы. В результате упрощается разработка, улучшается гибкость и прозрачность кода.
4.6.5. Инициатор для методов класса
До сих пор для вызова методов класса мы использовали преобразование вызовов. Однако, поскольку std::functionнепосредственно поддерживает вызов методов, появляется возможность реализовать специализированный инициатор для указанного случая. За основу возьмем инициатор из п. 4.6.2 и модифицируем его.
Как мы видели в реализации универсального аргумента (п. 4.5.3), для вызова метода класса первым параметром должен передаваться указатель на экземпляр класса. Поэтому, в инициатор необходимо добавить переменную для хранения этого указателя. Но поскольку тип класса заранее неизвестен, его следует задавать как параметр, т. е. инициатор должен быть объявлен в виде шаблона. Далее необходимо добавить метод для настройки указателя и, соответственно, при задании сигнатуры и выполнении вызова передавать дополнительный аргумент – указатель на экземпляр класса. Реализация приведена в Листинг 57.
template // (1)
class InitiatorForClass
{
public:
template
void setup(const CallbackArgument argument) // (2)
{
callbackHandler = argument;
}
void setupInstance (ClassName* classObject) // (3)
{
ptrClass = classObject;
}
void run() // (4)
{
int eventID = 0;
//Some actions
callbackHandler(ptrClass, eventID); // (5)
}
private:
std::function callbackHandler; // (6)
ClassName* ptrClass = nullptr; // (7)
};
В строке 1 объявлен шаблон класса. В строке 2 объявлен метод для настройки аргумента, в качестве которого выступает указатель на метод-член. В строке 3 объявлен метод для настройки экземпляра класса. Метод запуска 4 такой же, как и в исходном, за исключением того, что при вызове в аргумент дополнительно передается указатель на класс (строка 5). В строке 6 инстанциируется аргумент для вызова метода класса, в сигнатуре первым параметром выступает указатель на класс, задаваемый параметром шаблона-инициатора. В строке 7 объявлена переменная для хранения указателя на экземпляр класса.
Итак, модифицировав инициатор из Листинг 53 п. 4.6.2, мы реализовали отдельный инициатор для вызова методов-членов. Используя частичную специализацию шаблона, можно сделать так, чтобы оба инициатора объявлялись одинаковым способом (Листинг 58).
template // (1)
class Initiator
{
//… Implementation for origin initiator
};
template // (2)
class Initiator
{
//… Implementation for class method call initiator
};
В строке 1 объявлен исходный класс, но теперь он является шаблоном с пакетом параметров. Пакет параметров здесь не используется, он нужен только для дальнейшей специализации.
В строке 2 объявлен шаблон для вызова методов-членов. Поскольку его имя совпадает с именем предыдущего, компилятор будет считать, что здесь определяется не новый класс, а специализация объявленного ранее. В объявлении указан параметр, предполагается, что в этом качестве будет использоваться имя класса. Теперь, если при инстанциировании шаблона будет задаваться параметр, будет выбрана специализация для вызова методов-членов. При отсутствии параметров будет выбран исходный шаблон.
Использование двух типов инициатора (исходного и специализированного) для вызова методов класса приведено в Листинг 59, здесь используется преобразование вызовов из Листинг 54 п. 4.6.3.
class Executor
{
public:
static void staticCallbackHandler(Executor* executor, int eventID) {}
void callbackHandler(int eventID) {}
void operator() (int eventID) {}
};
int main()
{
Executor executor;
Initiator initiator; // (1)
initiator.setup(CallbackConverter(&Executor::callbackHandler, &executor)); // (2)
initiator.run();
Initiator initiatorForClass; // (3)
initiatorForClass.setup(&Executor::callbackHandler); // (4)
initiatorForClass.setupInstance(&executor); // (5)
initiatorForClass.run();
}
В строке 1 объявлен исходный инициатор. В параметры шаблона мы не передаем никаких аргументов, т. е. шаблон инстанциируется подобно обычному классу. В строке 2 происходит настройка инициатора, в качестве аргумента передается объект для преобразования вызовов.
В строке 3 объявлен специализированный инициатор для вызова методов класса, он инстанциируется типом Executor. В строке 4 настраивается указатель на метод класса, в строке 5 настраивается указатель на экземпляр класса.
Какой инициатор лучше использовать для методов класса, исходный с преобразованием или модифицированный с непосредственным вызовом? Трудно однозначно ответить на этот вопрос. С одной стороны, использование специализированного класса противоречит идее обобщенного кода – в специализированном классе мы вынуждены повторять всю реализацию, даже в тех частях, где она совпадает с исходной. С другой стороны, упрощается работа с настройкой инициатора – нам не нужно использовать класс для преобразования, можно по отдельности изменять указатель на метод и указатель на экземпляр. В общем, выбор остается на усмотрение разработчика.
Читать дальшеИнтервал:
Закладка: