Доброго времени суток. Хочу поведать уважаемому хабрачитателю одну интересную вещь с которой пришлось столкнуться в недавнем времени. В одной из задач, стоявших передо мной, требовалось реализовать класс. В рамках этого класса должны выполняться некоторые вычисления. Для простоты, а также в целях сохранения работоспособности основного кода, решено было использовать потоки. Но все оказалось не так тривиально.
Первоначальный вариант был таков:
// структура параметров передаваемых в поток
struct ArgsThread
{
int *tmp1;
char *tmp2;
int indata1;
char indata2;
};
// непосредственно поток выполняющий вычисления
unsigned long CalculationThread(void *arg)
{
ThreadArgs* args = reinterpret_cast<ThreadArgs*>(arg); // выкапываем входные данные
// что-то сумбурно и долго считаем
}
// класс который требуется реализовать
class MyCalc
{
private:
void *HandleThread;
unsigned long IdThread;
int tmp1;
char tmp2;
public:
MyCalc(int indata1, char indata2)
{
ArgsThread* args=new ArgsThread();
args->tmp1 = &tmp1; // упаковываем данные
args->tmp2 = &tmp2;
args->indata1 = indata1;
args->indata2 = indata2;
HandleThread = CreateThread(NULL, 0, &CalculationThread, args, 0, &IdThread); // создаем поток
};
~MyCalc
{
TerminateThread(HandleThreade, NULL); // завершаем выполнение потока
CloseHandle(HandleThread); // закрываем хендл
};
};
Поток находился вне класса, что, в принципе, удовлетворяло задаче. После раздумий пришла идея реализации потока внутри класса, а точнее, один из методов должен стать функцией, которую выполнял бы поток.
Недолго думая (что очень зря), родился такой код:
class MyCalc
{
private:
void *HandleThread;
unsigned long IdThread;
int tmp1;
char tmp2;
protected:
unsigned long CalculationThread(void *arg)
{
// что-то сумбурно и долго считаем
}
public:
MyCalc(int indata1, char indata2)
{
HandleThread = CreateThread(NULL, 0, &CalculationThread, this, 0, &IdThread); // создаем поток
};
~MyCalc
{
TerminateThread(HandleThreade, NULL); // завершаем выполнение потока
CloseHandle(HandleThread); // закрываем хендл
};
};
Естественно, во время компиляции я получил ошибку о том, что передаваемые в функцию
CreateThread параметры не корректны, а в частности прототип функции потока не соответствовал запрашиваемому типу. На одном из форумов было найдено решение, суть которого заключалась в том, что метод делаем
static. Что тоже в принципе удовлетворяло меня, но как оказалось поток не имел доступа ко внтренним данным класса. И в конечном итоге решено было сделать этот метод полноправным членом класса. После полу часа возни с компилятором родилось такое решение:
// объявляем необходимые типы
typedef unsigned long (__stdcall *ThrdFunc)(void *arg); // прототип функции потока
typedef unsigned long (__closure *ClassMethod)(void *arg); // прототип метода класса
// данное объединение позволяет решить несостыковку с типами
typedef union
{
ThrdFunc Function;
ClassMethod Method;
}tThrdAddr;
// для гибкости использования храним все в одном месте
typedef struct
{
void* Handle; // хэндл потока
tThrdAddr Addr; // адрес
unsigned long Id; // ID потока
unsigned long ExitCode; // код выхода
}tThrd;
class MyCalc
{
private:
tThrd MyThread;
protected:
unsigned long ThrdHandle(void *arg)
{
// что-то сумбурно и долго считаем
};
public:
MyCalc()
{
MyThread.Addr.Method = &ThrdHandle; // тут главная магия
MyThread.Handle = CreateThread(NULL, 0, MyThread.Addr.Function, this, 0, &MyThread.Id);
GetExitCodeThread(MyThread.Handle, &MyThread.ExitCode);
};
~MyCalc()
{
if(MyThread.Handle)
{
TerminateThread(MyThread.Handle, MyThread.ExitCode);
CloseHandle(MyThread.Handle);
}
};
};
Таким образом наш поток становится и членом класса одновременно, со всем вытекающими ООП возможностями. Отпадает необходимость в «огороде» из упаковки и распаковки аргументов содержащих данные, поскольку теперь есть доступ ко всем методам и полям класса. Таких методов-потоков можно реализовать бесконечно много, и пользователь класса не сможет получить к ним хоть какой-нибуть доступ (правила
private и
protected) или случайно вызвать выполнение кода содержащегося в методе-потоке.
P.S. При желании прототип метода может в корне отличатся от того, что объявлен в примере, ведь используется только его адрес, но не стоит забывать и о передаваемых параметрах.
UPD: данный код генерировался и оттачивался в среде C++ Builder, поэтому в прототипе метода присутствует
__closure. изменяя прототип вы можете без больших потерь и изменений использовать данный код в других компиляторах.