Сегодня речь зайдет о довольно специфичной проблеме, а именно о замыканиях. На текущий момент замыкания включены в новый стандарт C++11, и, собственно, данная проблема, как проблема, исчерпана. Для чего можно применить замыкания? Да для кучи разных задач. В данной конкретной задаче, замыкание было необходимо для реализации произвольного сообщения, которое посылалось одним потоком другому и выполняло в целевом потоке произвольный код.
На данный момент не все компиляторы на текущий момент хорошо поддерживают старые компиляторы. В данном случае необходимо было реализовать замыкания в довольно старой версии MSVC (2008) и MinGW с версией GCC 4.4.3. В них был необходим другой способ для работы с замыканиями, который бы упростил их создание и объявление до следующих этапов:
- Объявление переменных, сохраняемых в контексте функции. Сохраняться будут значения а не ссылки. Для сохранения ссылок стоит использовать smartpointers
- Объявление исполняемого кода замыкания
- Инициализация контекста функции внешними переменными
- Передача функции, как объекта во внешнюю функцию (к примеру, какую-нибудь функцию run)
Также довольно серьезным ограничением является то, что замыкание в данном случае не будет ничего возвращать (для возврата, в принципе, достаточно небольшой модификации решения.).
Довольно простым решением для данной задачи выглядит объявление анонимного класса, который содержит в себе необходимый контекст и метод для исполнения, при этом у данного класса создается объект, который инициализируется и передается во внешнюю функцию. У данного метода есть несколько легкорешаемых проблем:
- Нельзя объявить несколько переменных внутри одной функции (решается взятием объявления и инициализации в блок)
- Необходимо скопировать объект внутрь функции передачи (решается путем объявления функции копирования или созданием объекта на куче и другими способами, однако для этого у класса должно быть имя. Однако коррелирующие имена внутри блока кода в данных компиляторах не являются проблемой)
- Громоздкий синтаксис. Можно решить при помощи макросов.
Дополнительной проблемой в GCC 4.4.3. станет ещё и сигнатура функции run. В обычном случае достаточно шаблонной функции. Однако в нашем случае, инстанцирование не сработает с анонимным классом. Поэтому приходится использовать динамический полиморфизм. Итоговое решение выглядит cледующим образом:
#include <stdio.h>
/** Базовый класс, который будет использован для создания на
его основе функций замыкания
*/
class ClosureBasic
{
public:
/** Метод класса, который будет описывать замыкания
*/
virtual void run()=0;
virtual ~ClosureBasic() {}
};
/** Макрос, с которого должно начинаться определение замыкания
*/
#define CLOSURE { \
class ____: public ClosureBasic \
{ \
public: \
ClosureBasic * ___() \
{ \
return new ____(*this); \
}
/** Макрос для объявления переменных контекста.
Может быть опущен, однако удобен для сохранения общего стиля
*/
#define CLOSURE_DATA(X) X
/** Макрос для объявления кода замыкания (как общего метода)
*/
#define CLOSURE_CODE(X) virtual void run() { X }
/** Макрос для инициализации замыкания общим контекстом.
Просто конструирует объект по умолчанию и выполняет
код в нем.
*/
#define INITCLOSURE(X) } ______; X;
/** Завершающий макрос для передачи замыкания на исполнение
Может быть опущен, а вызов ______.___()
использован для передачи замыкания куда-либо
Главное - закрывающая фигурная скобка обязательна
*/
#define SUBMITCLOSURE run(______.___() ); }
/** Макрос для установки значения переменной внутри замыкания
*/
#define CLSET(PROP,VAL) ______. PROP = VAL;
/** Тестовая функция, которая просто выполняет код
замыкания
\param[in] cl сам объект замыкания
*/
void run(ClosureBasic * cl)
{
cl->run();
// Данная строка удаляет объект и удалив её,
// мы получим утечку памяти
delete cl;
}
int main(int argc, char** argv)
{
int a=55;
// Простой пример замыкания, которое берет
// переменную а и выводит её
CLOSURE
CLOSURE_DATA( int a; )
CLOSURE_CODE( printf("%d\n", a);
printf("%d\n", a);
)
INITCLOSURE( CLSET(a,a); )
SUBMITCLOSURE;
return 0;
}