Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > C/C++: Общие вопросы > Вызов виртуального метода потомка из базового


Автор: andrew_121 25.1.2010, 08:38
есть такой класс:
Код

struct thread_base {
    thread_base() {
        pthread_create(&thread, NULL, (void*(*)(void*))thread_func, (void*)this);
        pthread_join(thread, NULL);
    }
protected:
    virtual void run() = 0;
private:
    static void* thread_func(void* arg) {
        thread_base* pthread = reinterpret_cast<thread_base*>(arg);
        std::cout << "thread_func() arg = " << arg << std::endl << std::flush; /* это выводится */
        pthread->run();
    }
    pthread_t thread;
};

из него выведен такой:
Код

struct thread_stage1 : public thread_base {
    thread_stage1(double p1, double p2, const double& v)
    :thread_base(),min(p1),max(p2),val(v)
    {
        std::cout << "thread_stage1" << std::endl << std::flush; /* это не выводится */
    }
    virtual void run() {
        std::cout << "данные приняты первой ступенью" << std::endl << std::flush; /* и это не выводится */
    }
    private:
        double min;
        double max;
        const double& val;
};

при запуске вот что выводит:
Цитата

thread_func() arg = 0xbffe5b5c
pure virtual method called
terminate called without an active exception
Aborted

из этого понятно что программа рушится обрабатывая конструктор базового класса.
где ступил?
linux, g++

Автор: mes 25.1.2010, 09:48
Цитата(andrew_121 @  25.1.2010,  07:38 Найти цитируемый пост)
pure virtual method called


Цитата(andrew_121 @  25.1.2010,  07:38 Найти цитируемый пост)
обрабатывая конструктор базового класса.

нельзя в конструкторе прямо или косвенно вызывать чисто-виртуальные методы..
для (всех) виртуальных функций действует правило - вызывается тело виртуальной функции базового класса, так как наследник еще не сформирован..

Автор: andrew_121 25.1.2010, 10:17
mes, так "thread_func()" это статический метод. т.е. в потомке он будет(должен) вызывать переопределенный метод потомка. или не так?
в общем, как исправить?

Автор: Леопольд 25.1.2010, 10:29
Цитата(andrew_121 @  25.1.2010,  08:38 Найти цитируемый пост)
thread_base* pthread = reinterpret_cast<thread_base*>(arg);

Обязательно через void* передавать?


Так всё работает. Значит проблема в использовании.
Код

#include <iostream>

struct IParent{
protected:
    virtual void call() = 0;
public:
    static void delegateCall(IParent& iface){
        iface.call();
    }

    typedef void (*Delegate)(IParent&);
};
struct Child: public IParent{
    virtual void call(){
        std::cout<<"Child::call"<<std::endl;
    };
};
void caller(IParent::Delegate deleg){
    Child obj;
    deleg(obj);
}

int main(){

    caller(&IParent::delegateCall);
    
    return 0;
}

Автор: Alca 25.1.2010, 10:30
а если так?
Код

struct thread_stage1 : public thread_base {
    thread_stage1(double p1, double p2, const double& v)
    :thread_base(),min(p1),max(p2),val(v)
    {
        std::cout << "thread_stage1" << std::endl << std::flush; /* это не выводится */
    }
protected:  //<<<<<<<<<<<<<!!!!!!!!!!!!!!!!!!!!!!!!
    virtual void run() {
        std::cout << "данные приняты первой ступенью" << std::endl << std::flush; /* и это не выводится */
    }
    private:
        double min;
        double max;
        const double& val;
};

Автор: Lazin 25.1.2010, 10:30
andrew_121, у тебя run вызывается в конструкторе

Автор: Alca 25.1.2010, 10:34
Цитата

andrew_121, у тебя run вызывается в конструкторе

где именно?

Автор: mes 25.1.2010, 10:37
Цитата(andrew_121 @  25.1.2010,  09:17 Найти цитируемый пост)
так "thread_func()" это статический метод.


Цитата(mes @  25.1.2010,  08:48 Найти цитируемый пост)
прямо или косвенно

так в этой статической вызывается виртуальная smile

Цитата(andrew_121 @  25.1.2010,  09:17 Найти цитируемый пост)
в общем, как исправить? 

можно применить "статическую виртуализацию" (CRTP Patern) если полиморфизм не нужен
либо стартер-враппер  или принимать функциональный объект.
в любом случае перепроектировка класса.

Автор: andrew_121 25.1.2010, 10:46
Цитата(Леопольд @  25.1.2010,  10:29 Найти цитируемый пост)
Обязательно через void* передавать?

да. pthread_create() требует указатель на функцию типа void* (*)(void*)


Цитата(Lazin @  25.1.2010,  10:30 Найти цитируемый пост)
andrew_121, у тебя run вызывается в конструкторе 

точно! потомок же еще не создан. сейчас проверю.

Автор: Леопольд 25.1.2010, 10:53
Цитата(andrew_121 @ 25.1.2010,  08:38)
есть такой класс:
Код

struct thread_base {
    thread_base() {
        pthread_create(&thread, NULL, (void*(*)(void*))thread_func, (void*)this);
        pthread_join(thread, NULL);
    }

В конструкторе thread_base передаётся this в pthread_create. Тут потенциальная ошибка. Сначала создаются базовые классы, потом наследники. В конструкторе thread_base можно считать созданной только базовую часть объекта а значит в vtbl нет указателей на переопределённые функции потомка. Поэтому, прямо или косвенно, никак нельзя вызвать то чего ещё нет. По всей видимости именно такая попытка и делается.

Но ничего не мешает вызвать pthread_create из конструктора последнего потомка. Не стоит расценивать как предложение реализовать таким образом. Сам я бы так не сделал smile Код приведён для того, чтобы просто подтвердить вышенаписанное...

Код

#include <iostream>

struct IParent{
protected:
    virtual void call() = 0;
public:
    static void delegateCall(IParent& iface){
        iface.call();
    }

    typedef void (*Delegate)(IParent&);
};
void caller(IParent& ref, IParent::Delegate deleg){
    deleg(ref);
}
struct Child: public IParent{
    Child(){
        caller(*this, &(IParent::delegateCall));
    }
    virtual void call(){
        std::cout<<"Child::call"<<std::endl;
    };
};

int main(){

    Child();
    
    return 0;
}

Автор: andrew_121 25.1.2010, 11:03
добавил в базовый класс метод start(), и все заработало.
Код

/* базовый класс потока */
struct thread_base {
    thread_base() {}
    void start() {
        pthread_create(&thread, NULL, (void*(*)(void*))thread_func, (void*)this);
        pthread_join(thread, NULL);
    }
protected:
    virtual void run() = 0;
    static void* thread_func(void* arg) {
        thread_base* pthread = reinterpret_cast<thread_base*>(arg);
        pthread->run();
    }
private:
    pthread_t thread;
};

но любопытно. каким образом можно реализовать запуск потока из конструктора предка, без функциональных объектов?

Цитата(mes @  25.1.2010,  10:37 Найти цитируемый пост)
CRTP Patern

один из выходов.

Добавлено через 58 секунд
Цитата(Леопольд @  25.1.2010,  10:53 Найти цитируемый пост)
В конструкторе thread_base передаётся this в pthread_create. Тут потенциальная ошибка. Сначала создаются базовые классы, потом наследники. В конструкторе thread_base можно считать созданной только базовая часть объекта а значит в vtbl нет указателей на переопределённые функции потомка. Поэтому, прямо или косвенно, никак нельзя вызвать то чего ещё нет. По всей видимости именно такая попытка и делается. 

да, все верно.

Автор: mes 25.1.2010, 11:16
Цитата(andrew_121 @  25.1.2010,  10:03 Найти цитируемый пост)
но любопытно. каким образом можно реализовать запуск потока из конструктора предка, без функциональных объектов?

внешним  объектом/функцией который вначале создаст а потом запустит.

Автор: andrew_121 25.1.2010, 11:23
Цитата(mes @  25.1.2010,  11:16 Найти цитируемый пост)
внешним  объектом/функцией который вначале создаст а потом запустит.

понятно. smile
всем спасибо!
вопрос закрыт.

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)