Программерские типсы и триксы: Многопоточные классыСегодня практически все ОС поддерживают многозадачность. Потоки, процессы и прочие атрибуты сейчас уже не кажутся чем-то страшным и необычным. Мультипоточность используется практически в любом современном приложении. А еще в наше время очень активно применяется ООП. Сегодня мы узнаем, как в C++ подружить между собой и многопоточность, и ООП. Перед нами стоит задача написать класс, который будет запускать некоторый код в отдельном треде. Для этого будем использовать стандартную функцию Windows API — CreateThread. Более подходящим решением было бы применить _beginthread из стандартной библиотеки, но для отражения общей сути CreateThread вполне сгодится. Давай взглянем на код. Создание потока из класса
Все очень просто. Есть класс MyClass, который создает поток с помощью CreateThread. Потоковая функция ThreadFunc находится вне класса. Компилируется этот код без проблем, но есть несколько нюансов, из-за которых такое решение может оказаться неприемлемым. Основная проблема заключается в том, что мы не можем получить доступ к внутренним методам и переменным класса. Первое, что приходит в голову для решения этой задачки — сделать потоковую функцию членом класса. То есть примерно так: Потоковая функция — член класса
Но если попробовать скормить этот код компилятору, то мы получим ошибку, суть которой заключается в том, что тип потоковой функции не соответствует требуемому. Если копнуть глубже, то мы поймем, что компилятор просто не может понять, адрес какой именно ThreadFunc передавать в качестве третьего параметра в CreateThread. При этом объектов MyClass может быть создано несколько, и располагаться они могут в любом куске памяти, а API для создания треда требует указания точного адреса потоковой функции, который, понятное дело, становится известен лишь во время выполнения программы. Так что компилятор лишь разводит руками и предлагает еще немного пораскинуть мозгами. Следующее, что приходит в голову, это опять вынести ThreadFunc за пределы класса, но в этот раз сделать ее дружественной к MyClass с помощью директивы friend. Объявленная таким образом потоковая функция должна без проблем получить доступ к private членам MyClass. Но, так как ThreadFunc существует в единственном экземпляре, а объектов класса может быть множество, то нам придется передавать потоковой функции в качестве параметра указатель на создающий тред объект. Потоковая функция, дружественная классу
Все в этом примере хорошо, за исключением одного: к ThreadFunc можно обратиться в обход MyClass, а это не только делает бессмысленным создание отдельного класса для проектирования потока, но и нарушает инкапсуляцию — основной принцип объектноориентированного программирования. Потоковая функция имеет доступ к закрытым членам класса, а следовательно, любой желающий может их изменить, нарушая все законы ООП. Очевидно, что для решения проблемы с инкапсуляцией нам надо вернуть ThreadFunc обратно в пределы класса. Но, как мы уже видели выше, компилятор отказывается работать с таким кодом. Но так как функция-член ThreadFunc статическая, то, работая с другими членами MyClass, она не сможет корректно определить, с каким именно объектом класса ей нужно производить те или иные действия. Как и в случае с дружественной функцией, нам надо будет передавать потоку в качестве параметра указатель на текущий объект. Статическая потоковая функция — член класса
Теперь мы решили все наши проблемы. Внеся код треда в MyClass, мы обошли сложности с инкапсуляцией — теперь доступ к закрытым членам класса осуществляется только лишь из метода класса. А объявив ThreadFunc в private блоке, мы тем самым лишили пользователей нашего класса возможности вызвать потоковую функцию напрямую. Этот вариант решения нашей задачки можно назвать самым оптимальным и, казалось бы, остановиться на этом. Но есть еще один трюк, в основном для любителей C++ Builder, о котором я должен рассказать. Реализация компилятора от Борланда содержит в себе директиву __closure, которая служит для определения типа обработчика события. Вообще, обработчик события представляет собой указатель на функцию. Обычно этот указатель имеет 4-байтный размер, но при определении такого типа указателя функции передается еще и указатель this на экземпляр класса, поэтому указатель имеет 8-байтовый размер. Воспользовавшись этой директивой и немного подправив код, потоковую функцию уже можно не делать статической. Для большей наглядности посмотрим код: Использование __closure
Конечно, такое решение пригодно лишь для использования его в среде Builder, а многие программисты считают его грязным хаком. К тому же, использование объединений для решения проблем с типизацией — то еще извращение. Но, с другой стороны, если код работает корректно — значит, он имеет право на существование. ЗаключениеТеперь мы можем писать ООП-код, который выполняется в различных потоках. Перечисленные выше способы не являются единственными — возможно, кто-то придумал что-то более интересное для реализации данной задачи. Но для написания качественных программ этого вполне достаточно. |