Т.е. надо, наоборот, сделать асинхронный вызов.
Ну тогда задача разделяется на две:
1) Выбор одной из синхронных реализаций - обычная обёрточная функция
void foo(int a, int b)
{
#if FOO=1
foo1(a, b);
#elif FOO=2
foo2(a, b);
#else
throw exception();
#endif
}
2) Реализация асинхронного вызова по шаблону begin-end. Общее оформление абстрактной работы делается по шаблону "Команда" (Command) маленьким модулем.
task.h файл, который видит клиент:
#include <exception>
using namespace std;
class Work
{
protected:
virtual void _invoke() = 0;
public:
class Token;
static Work *invoke(Work *work);
static Token *beginInvoke(Work *work) throw(exception);
static Work *endInvoke(Token *token) throw(exception);
};
Основные положения:
1) Работа есть объект, аргументы и результаты работы которого спрятаны внутри и для механизма распараллеливания и синхронизации значения не имеют.
2) Работа попадает на вход, исполняется и оказывается на выходе, что конкретно она делает - скрыто внутри неё. Временем жизни объекта управляет пользователь.
3) Работа может выполняться синхронно - метод invoke.
4) Работа может выполняться асинхронно - методы beginInvoke, endInvoke. В этом случае идентификатором работы служит указатель типа Token, устройство которого пользователю знать не надо. Он обеспечивает привязку вызова endInvoke к ранее сделанному beginInvoke. На входе и на выходе по-прежнему один и тот же объект работы.
5) Обработка ошибок invoke не специфицирована: они могут быть, тогда объект работы назад не получим, их может не быть, тогда возвращается объект работы.
6) Обработка ошибок begin-end жёстко специфицирована. Либо работа запускается благополучно, либо по каким-то причинам это невозможно сделать, и тогда получаем исключение exception. Все прочие собственные ошибки работы в этой паре жёстко подавляются, и пользователь о них ничего не узнает (трансляция ошибок между потоками - это отдельная задача, которую мы здесь не решаем). Метод завершения работы никаких ошибок не возвращает, кроме той, что ему подсунули NULL вместо token.
7) Пользователю предлагается самостоятельно реализовать виртуальный метод _invoke, собственно выполняющий работу. Он не должен делать предположений о синхронном или асинхронном характере своего выполнения.
task.cpp файл реализации модуля с использованием WinAPI - системный пул потоков для асинхронных задач в оригинальном (WinNT) формате (в Win7/8 были усовершенствования - для обратной совместимости с WinXP и т.п. их не используем).
#include <Windows.h>
class Work::Token
{
private:
HANDLE event;
Work *work;
public:
Token(Work *work) :
event(NULL),
work(NULL)
{
if(work == NULL)
{
throw exception();
}
this->work = work;
this->event = CreateEvent(NULL, TRUE, FALSE, NULL);
if(this->event == NULL)
{
throw exception();
}
}
void begin()
{
BOOL result = QueueUserWorkItem(Work::Token::_work, this, 0);
if(result == FALSE)
{
throw exception();
}
}
void invoke()
{
try
{
Work::invoke(this->work);
}
catch(...)
{
// Обязательный перехват всех исключений, чтобы не сломался механизм синхронизации.
}
SetEvent(this->event);
}
Work *end()
{
WaitForSingleObject(this->event, INFINITE);
// В случае какой-либо ошибки ожидания всё равно ничего поделать не можем,
// так что просто возвращаем пользователю объект работы - пусть сам разбирается,
// насколько она удачно отработала.
return this->work;
}
~Token()
{
CloseHandle(this->event);
}
static DWORD WINAPI _work(LPVOID lpParameter)
{
if(lpParameter != NULL)
{
try
{
static_cast<Work::Token *>(lpParameter)->invoke();
}
catch(...)
{
// Обязательный перехват всех исключений внутри потока, чтобы не разрушить системный пул потоков.
}
}
return S_OK;
}
};
Work *Work::invoke(Work *work)
{
if(work == NULL)
{
throw exception();
}
work->_invoke();
return work;
}
Work::Token *Work::beginInvoke(Work *work)
{
Work::Token *token = NULL;
try
{
token = new Work::Token(work);
token->begin();
}
catch(...)
{
if(token != NULL)
{
delete token;
}
throw exception();
}
return token;
}
Work *Work::endInvoke(Token *token)
{
if(token == NULL)
{
throw exception();
}
return token->end();
}
Заодно модуль прячет от пользователя тот факт, что использует WinAPI для реализации асинхронных вызовов. И Token тут используется как шаблон PImpl для сокрытия "потрохов" механизма синхронизации.
Наконец, этот полученный модуль может быть применён для решения прикладной задачи - вызова процедуры foo. Как известно, любая процедура имеет входные, выходные и входные-выходные параметры. В частности результат функции может рассматриваться как один из выходных параметров. Для общности примера сделаем такую функцию, которая что-то возвращает.
int foo(int a, int b);
class Foo :
public Work
{
private:
int a;
int b;
int result;
protected:
virtual void _invoke()
{
this->result = foo(this->a, this->b);
}
public:
Foo() :
a(0),
b(0),
result(0)
{}
void putA(const int a)
{
this->a = a;
}
void putB(const int b)
{
this->b = b;
}
int getResult() const
{
return this->result;
}
static Token *beginInvoke(int a, int b)
{
Foo *instance = new Foo();
instance->putA(a);
instance->putB(b);
return Work::beginInvoke(instance);
}
static int endInvoke(Token *token)
{
Foo *instance = dynamic_cast<Foo *>(Work::endInvoke(token));
if(instance == NULL)
{
// Случай, когда подсунули "чужой" token с задачей другого типа.
throw exception();
}
int result = instance->getResult();
delete instance;
return result;
}
static int invoke(int a, int b)
{
Foo instance;
instance.putA(a);
instance.putB(b);
Work::invoke(&instance);
return instance.getResult();
}
};
Общий подход такой:
- любой входной параметр x имеет метод установки значения putX,
- любой выходной параметр x имеет метод получения значения getX,
- любой входной-выходной параметр имеет, соответственно, оба метода,
- результат рассматривается как выходной параметр с именем result.
Для удобства использования объект самостоятельно реализует вспомогательные методы beginInvoke, endInvoke и invoke, которые являются обёрткой над вызываемой функцией. Метод invoke имеет в точности сигнатуру вызываемой функции. Метод beginInvoke содержит лишь входные и входные-выходные параметры и возвращает Token. Метод endInvoke принимает Token и содержит лишь выходные и входные-выходные параметры, а также возвращает результат.
И этим всем можно пользоваться - хоть синхронно:
int a = 1, b = 2, c;
c = Foo::invoke(a, b);
хоть асинхронно:
int a = 1, b = 2, c;
Work::Token *token = Foo::beginInvoke(a, b);
c = Foo::endInvoke(token);
Главное во всём этом - систематическое применение вышеописанных правил оформления вызовов функций для решения всех подобных задач. И тогда внутри программы будет содержаться определённое архитектурное решение.
Если критична производительность, то вместо виртуального метода _invoke можно сооружать всякие template-конструкции, типа функторов стандартной библиотеки для <algorithm>.