2013-02-18 6 views
0

에서 캐스팅 때 다음과 같은 코드가 있습니다C++ 다형성 문제 무효 *

#include <windows.h> 
#include <iostream> 

static DWORD __stdcall startThread(void *); 

class BaseClass { 
private: 

    void threadLoop() { 
     // stuff ... 
     std::cout << someStuff() << std::endl; 
     // stuff ... 
    } 
protected: 
    HANDLE handle; 
    virtual int someStuff() { 
     return 1; 
    } 
public: 
    friend DWORD __stdcall startThread(void *); 

    BaseClass() { 
     handle = 0; 
    }; 

    void start() { 
     handle = CreateThread(NULL, 0, startThread, this, 0, NULL); 
    } 

    ~BaseClass() { 
     if(handle != 0) { 
      WaitForSingleObject(handle, INFINITE); 
      CloseHandle(handle); 
     } 
    } 
    // stuff 
}; 

static DWORD __stdcall startThread(void *obj_) { 
    BaseClass *obj = static_cast<BaseClass *>(obj_); 

    obj->threadLoop(); 
    return 0; 
} 

class DerivedClass : public BaseClass { 
public: 
    virtual int someStuff() { 
     return 2; 
    }; 
}; 

int main() { 
    BaseClass base; 
    base.start(); 
    DerivedClass derived; 
    derived.start(); 
} 

는 모든 인스턴스가 WINAPI와 도우미 함수 startThread를 사용하여 스레드를 생성을하는 대의원 다시 개체의 방법 threadLoop를 호출하는 스레드를 만들었습니다. 이제 문제는 threadLoop이 다른 가상 메소드를 호출하지만 virual 메소드의 다른 구현으로 파생 클래스를 작성하면 다형성이 작동하지 않는 것입니다.

왜? 이 문제를 어떻게 해결할 수 있습니까?

편집 : 코드를 업데이트 했으므로 스레드가 생성자에서 시작되지 않습니다.

+0

파생 클래스의 인스턴스를'startThread()'에 전달 하시겠습니까? – Nim

+1

휴대용 표준 C++ 11 스레드를 사용할 수 있다는 것을 알고 계십니까? std :: async? 독점 API 나 악의적 인 캐스트에 간섭 할 필요가 없습니다. – sehe

+0

@sehe 그리고 C++ 11에 대한 액세스 권한은 무엇이라고 생각합니까? –

답변

1

코드 작성에는 개체 생성 중에 스레드를 만들고 실행하는 등 몇 가지 문제가 있습니다. 그것은 가장 나쁜 디자인입니다.

깨끗한 디자인 thread라는 추상 클래스에서 스레드 기능을 캡슐화하는 것, 그리고 예를 들어, run 메서드를 재정의, 그것에서 파생 : 그것에서

class thread : public noncopyable 
{ 
protected: 
    HANDLE m_hthread; 
    unsigned long m_id; 
private: 
    static unsigned long __stdcall start(void* args) 
    { 
     static_cast<thread*>(args)->run(); 
     return 0; 
    } 
public: 
    thread(); 
    virtual ~thread(); 
    virtual bool start() 
    { 
     if (m_hthread != nullptr && isrunning(m_hthread)) 
     { 
      throw std::logic_error("Cannot start thread, as it is already running."); 
     } 
    m_hthread = ::CreateThread(NULL, 0, start, this, 0, &m_id); 
    return m_hthread != nullptr; 
    } 
    unsigned long get_id() const; 
    virtual unsigned long wait(); 
protected: 
    virtual void run() = 0; 
}; 

을 그리고 파생 :

class worker : public thread 
{ 
    protected: 
     virtual void run() override; 
}; 

그리고 당신은으로 사용할 것 :

worker workerObject; 
workerObject.start(); 

//do other works here 
//maybe create few more threads; 

workerObject.wait(); //wait for worker to complete! 
+0

아주 좋은 디자인이라고 확신하지 못합니다. 훨씬 더 나은, 나는 생각하기에, 별도의'Thread' 클래스가 될 것이고, 생성자는 aThreadable (또는'Runnable', 또는 무엇이든) 객체에 대한 포인터를 취합니다. (하지만 코드가 포함 된 객체의 생성자에서 스레드를 시작하지 않는 한 둘 다 작동합니다.) –

+0

@JamesKanze : 동의 함. 그것은 OP보다 낫다. 그러나 훨씬 더 나은 디자인을 위해서는 더 많은 사고와 타이핑이 필요하므로 피할 수 있습니다. – Nawaz

+0

@JamesKanze : 여기 추천은 훨씬 좋습니다 : http://www.drdobbs.com/parallel/prefer-futures-to-baked-in-async-apis/222301165. Resume 기능에는 몇 가지 문제가 있지만 Thread의 .NET 디자인은 Java보다 훨씬 낫습니다. – Nawaz

6

파생 된 객체를 완성하기 전에 스레드를 시작합니다. 이는 정의되지 않은 동작입니다 ( 은 생성중인 스레드에서 여전히 코드를 실행중인 일 때 새 스레드의 객체에 액세스 할 가능성이 높으므로). 당신은 별도의 건설과 스레드를 시작해야합니다.

편집 : 문제는 이런 종류의 처리

한 가지 방법 :

class Threadable 
{ 
public: 
    virtual Threadable() {} 
    virtual run() = 0; 
}; 

DWORD __stdcall startThread(void* object) 
{ 
    static_cast<Threadable*>(object)->run(); 
} 

class Thread 
{ 
    std::auto_ptr<Threadable> myThread; 
    HANDLE myHandle; 
public: 
    Thread(std::auto_ptr<Threadable> thread) 
     : myThread(thread) 
     , myHandle(CreateThread(NULL, 0, startThread, myThread.get(), 0, NULL)) 
    { 
    } 
    ~Thread() 
    { 
     if (myHandle != NULL) { 
      WaitForSingleObject(myHandle, INFINITE); 
      CloseHandle(myHandle); 
     } 
    } 
}; 

을 다음 BaseClassDerivedClassThreadable에서 파생 당신이, 그리고 그들을 호출 :

Thread base(std::auto_ptr<Threadable>(new BaseClass)); 
Thread derived(std::auto_ptr<Threadable>(new DerivedClass)); 

이 완벽하지 않습니다 (나는 더 많거나 적은 무제한 대기를 좋아하지 않습니다. de structors)하지만 시작하기에 충분해야합니다. (Modulo 위 코드의 오타가 —입니다. 테스트 해 보지 않았습니다.)

+0

@Mogria 당신은 생성자로부터'start'를 호출합니까? 그렇다면 여전히 동일한 문제가 있습니다. 요점은 파생 클래스의 하위 객체의 생성이 생성자가 실행을 완료 할 때까지 시작되지 않는다는 것입니다. 즉, 생성자가 완료되기 전에 스레드를 시작하면 스레드가 "실제로"시작할 때 파생 클래스가 존재하거나 존재하지 않을 수 있습니다. 즉, 존재 여부와 상관없이 경쟁 조건이됩니다. – rlc

+0

@rlc no 주 함수에서'start'를 호출했습니다. – MarcDefiant

+0

@ric 또는 반대의 방법으로 표현됩니다 : 가장 많이 파생 된 객체의 생성자가 완료 될 때까지 스레드를 시작하지 않아야합니다. 이것을 달성하는 데는 여러 가지 방법이 있습니다 :'start' 메소드를 사용하고 객체가 완전히 생성 된 후에 클라이언트 코드가 호출되도록 요구하고, 스레드 객체에서 다형성을 사용하지 말고 완전히 생성 된 스레드 가능 객체에 대한 포인터를 전달하십시오 , 템플릿 및 다중 상속과 관련된 좀 더 복잡한 솔루션을 제공합니다. –