Pull to refresh

Comments 37

Спасибо. Когда то давно с этим же сталкивался.
Можно было, конечно, пойти по пути наследования какого-нибудь готового класса из таких библиотек как: VCL (классTThread) иди Qt (классQThread), но тогда мы бы ограничивались лишь реализацией одного единственного потока в рамках данного класса. А тут мы получаем возможность реализовывать многопоточные классы, которые с легкостью переносятся на другие платформы.
Свою проблему я как раз решил переходом на Qt.
Стоило бы упомянуть что __closure – расширение языка в реализации C++ Builder, подобное лихачество непереносимо и небезопасно.
Полностью с вами согласен, поскольку данный код оттачивался в среде C++ Builder, в примере было использовано данное выражение. Справедливо, что для других компиляторов оно будет свое и прототип метода класса изменится.
а не было бы проще это сделать как-то так?
class MyCalc
{
private:
	void *HandleThread;
	unsigned long IdThread;
	
	int tmp1;
	char tmp2;

	static DWORD WINAPI ThreadF(void *arg)
	{
		return ((MyCalc*)arg)->CalculationThread();
	}
	
protected:
	unsigned long CalculationThread()
	{
		/////////
	}
	
public:
	MyCalc(int indata1, char indata2)
	{
		HandleThread = CreateThread(NULL, 0, ThreadF, this, 0, &IdThread);
	};
	
	~MyCalc()
	{
		TerminateThread(HandleThread, NULL);
		CloseHandle(HandleThread);
	};
};
ваше решение абсолютно справедливо. в своем примере я лишь хотел показать, как сделать потоком метод класса, со всеми плюшками ООП. ваш случай не защищен от вызова функции ThreadF только классом. к тому же она не является его членом, хоть и описана внутри.
если ThreadF была бы friend, то да, можно было бы вызвать её из вне класса. в моём примере она является static и находится в блоке private, что как раз и защищает её от вызова из вне и сохраняет её принадлежность классу. по крайней мере в Visual Studio это работет по такому принципу
в C++ Builder ваш пример потребовал необходимого преобразования типов, или же объявления ThreadF как __stdcall. также в вашем случае вы не имеете полного доступа к данным класса непосредственно из потока без указания пренадлежности к объекту класса, поэтому вынужденны делать дополнительный метод. в моем примере достаточно лишь выполнить необходимое преобразование типов для адресов метода и функции, после чего метод становится потоком и может получать беспрепятственный доступ данным класса. к тому же при использовании static при динамическом создании и удалении экземпляра класса есть вероятность, что метод ThreadF останется в памяти, что может грозить некоторыми ее утечками.
__closure — если бы он еще всеми компиляторами поддерживался бы.
Так что да, лучше не занимать любовью себе мозги и передавать указатель на this в аргументе.
мой способ не претендует на оригинальность. вы всегда можете воспользоваться другими или привести прототип метода к необходимому типу, это ничуть не усложнит и почти не изменит код.
Тут уже писали — использовать __closure — это читерство! :) Существует стандартный метод — попробуйте использовать такое колдунство, как указатель на метод класса, тогда код станет собираться и работать с любым компилятором.
как уже говорилось выше и в топике, вы можете изменять прототип метода как вам угодно. я лишь показал один из возможных примеров реализации.
Ну зачем везде городить ифдефы, если можно сразу всё написать по-человечески?
в чем же тогда заключается не человечность примера из топика, и где вы увидели в нем использование препроцессора?
Ну приведите пример без __closure для Visual C++
не имею под рукой данного компилятора, но думаю это будет работать:
typedef unsigned long (ClassName:: *ClassMethod)(void *arg); // прототип метода класса ClassName
А препроцессоры придется ставить именно для определения компилятора например.
а зачем его определять, например, если разработка ведется в конкретной среде с, вполне, явным компилятором, к чему тогда использовать препроцессор для определения его типа, или вы при написании кода одновременно используете множество компиляторов? согласен что его использование возможно для обеспечения кроссплатформенности, но в рабочем проекте, который уже отлажен и написан под вполне конкретные условия, не вижу смысла добиваться кросскомпиляции.
>а зачем его определять, например, если разработка ведется в конкретной среде с, вполне, явным компилятором

Так говорят только программисты-неудачники. Увы, но во всех книжках, на всех учебных курсах говорят о том, что нужно писать масштабируемый и переносимый код, чтобы не иметь геморроя в будущем.
Пожалуй, это стоит перенести в блог «Ненормальное программирование».
UFO just landed and posted this here
с boost::thread я банально не очень хорошо знаком. а beginthread(ex) не использую потому, что пример тривиальный и лишь для наглядности был приведен, но также и по причине того, что в потоке не используются вызовы CRT, в остальном согласен — лучше использовать beginthread(ex).
Бред какой-то

>UPD: данный код генерировался и оттачивался в среде C++ Builder, поэтому в прототипе метода присутствует __closure. изменяя прототип вы можете без больших потерь и изменений использовать данный код в других компиляторах.

Если бы я увидел такое в коде коллеги, послал бы его переписывать всё нафиг.
Согласен. Всё это вывглядит как кастыли. Но он молодец, что написал статью на хабр. Главное, чтобы он всю критику нормально воспринял. Эта статья с комментариями довольно поучительна!
с костылем соглашусь. но ведь это работает. и вполне стабильно. просто на момент написания данного фрагмента это решение было одним из явных и реализуемых на мой взгляд. кто-то решил бы по другому и интересно было бы взглянуть как.
а что не так в них, и вообще во всем что в топике, почему сразу бред? можно увидеть вполне конкретные указания на наличие ошибок или тех мест где бред, или других возможных вариантов решения задачи? интересно было бы прочитать и обоснование ваших выводов.
Юзать нормальные API например. И не считать себя умнее компилятора. union'ы и reinterpret_cast'ы вовсе не для таких случаев придумали.
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
спасибо за критику. признаю ошибки. про TerminateThread даже не догадывался о возможных последствиях. но тогда к вам вопрос. как же корректно поступить с завершением и уничтожением потока в данном случае?
UFO just landed and posted this here
Наверное это можно было бы реализовать следующим образом, только зачем?..
template<typename T>
struct tThrd
{
    typedef DWORD(__thiscall T::* PMethod)();
    static DWORD WINAPI Function(PVOID pParam)
    {
        return (((tThrd*)pParam)->pThis->*((tThrd*)pParam)->pMethod)();
    };
    T*          pThis;
    PMethod     pMethod;
    HANDLE      Handle;
};

class MyCalc
{
public:
    MyCalc()
    {
        // здесь нет никакой магии :)
        MyThread.pThis = this;
        MyThread.pMethod = &MyCalc::ThrdHandle;
        MyThread.Handle = CreateThread(NULL, 0, MyThread.Function, &MyThread, 0, NULL);
    }
    ~MyCalc()
    {
        WaitForSingleObject(MyThread.Handle, INFINITE);
        CloseHandle(MyThread.Handle);
    }
public:
    DWORD ThrdHandle()
    {
        // что-то сумбурно и долго считаем
        Sleep(10000);
        return 1;
    }
private:
    tThrd<MyCalc> MyThread;
};
или даже вот так:
template<typename T, DWORD(__thiscall T::* pMethod)()>
DWORD WINAPI Function(PVOID pParam)
{
    return ((T*)pParam->*pMethod)();
};
...
CreateThread(NULL, 0, Function<MyCalc, &MyCalc::ThrdHandle>, this, 0, NULL);
Sign up to leave a comment.

Articles