LINUX.ORG.RU

[C++] pthread_create в абстрактном классе

 


0

0

Хотелось бы без лишних зависимостей портабельно использовать pthreads
в С++. К огромному удивлению оказалось, что задача нетривиальна.
Первые трудности уже преодолел:

для вызова pthread_create() не использую ни функцию-член класса, ни
самостоятельную функцию за пределами класса, а только последнюю
(thread_func) с модификатором "extern C", которая вызывает нужную
функцию класса run.

Таким образом декларация класса PThread и thread_func находятся в
одном заголовочном файле.

Но вот из-за этой thread_func и возникла ещё одна проблема. Выходит,
что если более одного класса наследуют PThread, то при линковке
выходит столкновение двух "одинаковых" вариантов thread_func.

И namespace не помогут, ведь thread_func объявлена и определена в
абстрактном классе.

Посоветуете что-нибудь кроме как забыть об абстрактном PThread?

★★★★★

> Посоветуете что-нибудь кроме как забыть об абстрактном PThread?

Да, забыть. И запомнить -- шаблоны сильнее классов.

Посмотри, как твоя проблема решена в boost -- там есть нити.

www_linux_org_ru ★★★★★
()

Но если тебе позарез нужно *именно* как ты сказал -- то через шаблоны такое тоже делается.

www_linux_org_ru ★★★★★
()

Хотя возможно я переоценил твои проблемы

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg);

Т.к. статическая start_routine принимает arg, то в него можно например положить указатель на наследника PThread

void* PThread::start_routine(void* arg) { PThread* pt=(PThread*)arg; return arg->start(); }

и потом

int PThread::create(pthread_t *thread, const pthread_attr_t *attr)

{ return pthread_create(thread, attr, PThread::start_routine, this); }

www_linux_org_ru ★★★★★
()
Ответ на: комментарий от www_linux_org_ru

Говорят, что работа

{ return pthread_create(thread, attr, PThread::start_routine, this); }

не гарантируется стандартом С++. Т.к. pthread_create() требует функцию с C-линковкой, а член класса - C++-линковка. Оно под Линуксом, скорее всего, работает, но не рекомендуется.

А с шаблонами вряд ли выйдет, ведь thread_func() в заголовочном файле не в теле класса. Так что шаблон ли этот класс или нет - ничего не изменит.

gag ★★★★★
() автор топика
Ответ на: комментарий от gag

Я думаю, что тебе следует определиться какого уровня портабельности ты хочешь достичь: реального - для некоторого множества платформ, или бесполезного - с кучей трюков там, где в большинстве случаев это не нужно.

По теме: pthreads нормально используется в C++ проектах, а приведенный www_linux_org_ru подход является общеупотребительным в деле заворачивания pthreads в классы.

mannaz
()
Ответ на: комментарий от www_linux_org_ru

>{ return pthread_create(thread, attr, PThread::start_routine, this); }

PThread::start_routine убьет всю фишку, все преимущество виртуализации данной функции.

xydo ★★
()

имхо, не так уж и критично, если твоя thread_func будет без модификатора "extern C", а с модификатором static.
"extern C" вообще нужен только для формирования имен функций в объектных файлах, а в pthread_create используется исключительно ее адрес.

xydo ★★
()
Ответ на: комментарий от xydo

> PThread::start_routine убьет всю фишку, все преимущество виртуализации данной функции.

все "преимущество" в виртуализации функции start():

void* PThread::start_routine(void* arg) { PThread* pt=(PThread*)arg; return pt->start(); }

mannaz
()
Ответ на: комментарий от xydo

> имхо, не так уж и критично, если твоя thread_func будет без > модификатора "extern C", а с модификатором static. > "extern C" вообще нужен только для формирования имен функций в > объектных файлах, а в pthread_create используется исключительно ее > адрес.

Ну, я раньше на такое не обращал особого внимания, пока не прочитал

http://groups.google.ca/group/comp.programming.threads/msg/f5c244ebe83e1811?h...

от якобы архитектора POSIX Thread, и открыл здесь тему, чтобы прояснить вопрос.

gag ★★★★★
() автор топика
Ответ на: комментарий от mannaz

> или бесполезного - с кучей трюков там, где в большинстве случаев это не нужно.

Ну, в том то и дело, что я хочу как раз без трюков, а так, как положено.

> приведенный www_linux_org_ru подход является общеупотребительным в деле заворачивания pthreads в классы.

Я же в описании проблемы первым делом сказал, что этот способ отметается, т.к. якобы противоречит требованию pthread_create() из-за стандарта С++ (смотри ссылку выше). Так что трюком было бы применить как раз этот способ :)

> По теме: pthreads нормально используется в C++ проектах

Пока надеюсь на дальнейшие предложения. Иначе, надо будет попробовать туда заглянуть.

На данный момент выходит, что наследовать от PThread вряд ли можно (в Qt это тем не менее удается). А вот подход Джавы, когда есть класс, который реализует всего лишь интерфейс (т.е. гарантирует наличие метода run()), а сам поток создается специальным классом, который получает как параметр объект класса, который мы хотим запустить, смотрится вполне реализуемым с выше названными ограничениями.

А, казалось бы, у Джавы как раз нет таких проблем как у С/С++ и ей не надобно так реализовывать потоки в отличие от С++.

gag ★★★★★
() автор топика

Я нить делал так (базовый тред абстрактный) (там еще было несколько примочек, может быть не очень безопасных и не очень правильных, я их убрал из примера):

//header

class thread{ protected: pthread_t _thread; public: inline thread(){} virtual ~thread(){this->wait();} virtual void run()=0; void start(); void terminate(); void wait(); };

//source

static void *caller(void *obj){ reinterpret_cast<thread*>(obj)->run(); return 0; }

void thread::wait(){ pthread_join(this->_thread,0); }

void thread::start(){ pthread_create(&this->_thread,0,caller,this); }

void thread::terminate(){ pthread_detach(this->_thread); pthread_cancel(this->_thread); }

staseg ★★★★★
()
Ответ на: комментарий от staseg

//header

class thread{
protected:
pthread_t _thread;
public:
inline thread(){}
virtual ~thread(){this->wait();}
virtual void run()=0;
void start();
void terminate();
void wait();
};

//source

static void *caller(void *obj){
reinterpret_cast<thread*>(obj)->run();
return 0;
}

void thread::wait(){
pthread_join(this->_thread,0);
}

void thread::start(){
pthread_create(&this->_thread,0,caller,this);
}

void thread::terminate(){
pthread_detach(this->_thread);
pthread_cancel(this->_thread);
} 

staseg ★★★★★
()
Ответ на: комментарий от gag

Хорошо, что мешает тебе объявить в .cpp файле своего класса статическую функцию thread_func, не являющуюся членом класса, с extern "C" и вызвать в ней виртуальную функцию класса?

mannaz
()
Ответ на: комментарий от staseg

Кстати, кутешный QThread передает в pthread_create именно указатель на статический метод класса - это также говорит о том, что в большинстве приличных компиляторов под unix для функций C и C++ calling convention совпадают, хотя это и не гарантируется стандартом C++.

mannaz
()
Ответ на: комментарий от mannaz

> Хорошо, что мешает тебе объявить в .cpp файле своего класса статическую функцию thread_func, не являющуюся членом класса, с extern "C" и вызвать в ней виртуальную функцию класса?

Идеальный вариант, т.к. и в .h можно засунуть, что позволило бы каждому классу, наследованному от PThread иметь свою уникальную thread_func() :) Но, пробовал - extern и static, похоже, несовместимы.

gag ★★★★★
() автор топика
Ответ на: комментарий от mannaz

> Кстати, кутешный QThread передает в pthread_create именно указатель на статический метод класса - это также говорит о том, что в большинстве приличных компиляторов под unix для функций C и C++ calling convention совпадают, хотя это и не гарантируется стандартом C++.

Да, прикольно выходит. Одни написали pthread, другие стандарт C++. И то, и то вобщем-то очень популярно. Все довольны, но совместить их можно, по-видимому, только нарушив правила (как, ты говоришь, и уже сделали в Qt). Поражаюсь!

Кстати, не знаешь, случайно, а Qt под Виндой от кого потоки использует?

gag ★★★★★
() автор топика
Ответ на: комментарий от gag

Ты слишком рано начал разводить пафос :-)

Совместить их вполне можно, и именно так, как я тебе написал.

Для особо боязливых вместо PThread::start_routine написан PThread__start_routine и исправлена 1 опечатка.

www_linux_org_ru ★★★★★
()
Ответ на: комментарий от gag

#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>

void* PThread__start_routine(void* arg);

class PThread
{
    friend void* PThread__start_routine(void* arg);
    public:
        int create(pthread_t *thread, const pthread_attr_t *attr)
        {
            return pthread_create(thread, attr, PThread__start_routine, this);
        }
        virtual ~PThread() {}
    protected:
        virtual void* start()=0;
};
void* PThread__start_routine(void* arg)
{
    PThread* pt=(PThread*)arg;
    return pt->start();
}

int retval=7;
class My_PThread: public PThread
{
    public:
        My_PThread(int i): j(i) {}
    private:
        int j;
        virtual void* start() { printf("j=%d\n", j); return &retval; }
};

int main()
{
    pthread_t thread;
    My_PThread t(123);
    printf( "%d\n", t.create(&thread, NULL) );
    return 0;
}

www_linux_org_ru ★★★★★
()
Ответ на: комментарий от mannaz

> Кстати, кутешный QThread передает в pthread_create именно указатель на статический метод класса - это также говорит о том, что в большинстве приличных компиляторов под unix для функций C и C++ calling convention совпадают, хотя это и не гарантируется стандартом C++.

Я не смотрел код, но чувак видимо собрался привести C::f() к f(*C), и про это ему сказали "фы":

There's no rule that C and C++ must follow the same calling conventions. In many cases, they do; or at least it's "close enough" that a C function with a single argument and a class member function with no (explicit) arguments are the same. Counting on that, though, is just asking for trouble.

www_linux_org_ru ★★★★★
()
Ответ на: комментарий от mannaz

> Кстати, кутешный QThread передает в pthread_create именно указатель на статический метод класса - это также говорит о том, что

что иначе придется эту функцию, сделанную вместо статического метода класса, объявить френдом и инкапсуляция пропадет.

www_linux_org_ru ★★★★★
()
Ответ на: комментарий от www_linux_org_ru

Понятно, что pthread_t thread надо занести в класс и сделать много еще чего, но я тут не собирался полноценную обертку делать.

www_linux_org_ru ★★★★★
()
Ответ на: комментарий от gag

> Идеальный вариант, т.к. и в .h можно засунуть, что позволило бы каждому классу, наследованному от PThread иметь свою уникальную thread_func() :) Но, пробовал - extern и static, похоже, несовместимы.

Взгляни на последние исходники www_linux_org_ru

> Все довольны, но совместить их можно, по-видимому, только нарушив правила (как, ты говоришь, и уже сделали в Qt).


Дело в том, что Qt поддерживает конкретные компиляторы. С этой точки зрения я не вижу никаких нарушений. О каких правилах ты говоришь? Читал ли ты стандарт C++?

> Кстати, не знаешь, случайно, а Qt под Виндой от кого потоки использует?


От винды. Соответственно, входная функция там явно объявлена с stdcall.

mannaz
()
Ответ на: комментарий от www_linux_org_ru

> что иначе придется эту функцию, сделанную вместо статического метода класса, объявить френдом и инкапсуляция пропадет

Не знаю как сейчас, но в некоторых местах Qt объявление extern "C" все же использовалось при передаче функций C++ в качестве callback чистым сишным функциям. Кое-что от этого осталось и в исходниках QThread, но не используется. Возможно, отменили поддержку какого-либо legacy-компилятора, ХЗ.

mannaz
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.