virtual Thread in class



  • Hallo Leute,

    Ich habe eine Klasse Window die den Thread windowThread() bei Kunstruktion startet.
    Dann habe ich noch eine Klasse SpecialWindow die von Window abgeleitet ist.
    SpecialWindow hat auch einen windowThread() aber mit anderem Inhalt.

    class Window
    {
    private:
    	static DWORD WINAPI windowThread(LPVOID data)
    	{
    		printf("Window\n");
    		return 0;
    	}
    protected:
    	HANDLE _hWindowThread;
    public:
    	Window()
    	{
    		_hWindowThread = CreateThread(NULL, 0, windowThread, NULL, 0, NULL);
    	}
    };
    
    class SpecialWindow : public Window
    {
    private:
    	static DWORD WINAPI windowThread(LPVOID data)
    	{
    		printf("SpecialWindow\n");
    		return 0;
    	}
    public:
    	SpecialWindow()
    	{
    		_hWindowThread = CreateThread(NULL, 0, windowThread, NULL, 0, NULL);
    	}
    };
    

    Auch im SpecialWindow-Konstruktor wird ein Thread gestartet.

    Da beim aufruf von SpecialWindow() auch Window() aufgerufen wird, habe ich am ende zwei Threads.
    Ich hab es dann so gemacht:

    class Window
    {
    private:
    	HANDLE _hWindowThread;
    	static DWORD WINAPI windowThread(LPVOID data)
    	{
    		printf("Window\n");
    		return 0;
    	}
    protected:
    	Window(void *specialWindow) // <==========
    	{
    	}
    public:
    	Window()
    	{
    		_hWindowThread = CreateThread(NULL, 0, windowThread, NULL, 0, NULL);
    	}
    };
    
    class SpecialWindow : public Window
    {
    private:
    	HANDLE _hWindowThread;
    	static DWORD WINAPI windowThread(LPVOID data)
    	{
    		printf("SpecialWindow\n");
    		return 0;
    	}
    public:
    	SpecialWindow() :
    	  Window(NULL) // <==========
    	{
    		_hWindowThread = CreateThread(NULL, 0, windowThread, NULL, 0, NULL);
    	}
    };
    

    Window(void *specialWindow) ist nun der Kunstruktor der aufgerufen wird, wenn das Objekt ein SpecialWindow ist.

    Mein Frage ist nun: Macht man das so? 😕



  • Nein.



  • sondern?



  • Man lässt einen Thread von außen erstellen.



  • Versteh ich nicht...
    Wie soll das gehen?



  • std::thread window_thread([&] {
      window w;
      w.get_all();
    });
    


  • was ist denn get_all()?



  • Das ist die GetMessage-Schleife.



  • Gibt es auch eine Alternative ohne std::thread (mein Compiler unterstützt kein C++11)



  • boost::thread?



  • Geht das auch ohne boost?



  • Ohne Boost und ohne C++11 (oder TR1):

    Thread.hpp

    #pragma once
    
    #include <exception>
    
    #include <Windows.h>
    
    class SystemError
        : public virtual std::exception
    {};
    
    class Thread
    {
    public:
        class Callback
        {
        public:
            virtual DWORD run() = 0;
    
        protected:
            ~Callback() {}
        };
    
        // throw(SystemError) if CreateThread(..) fails
        explicit Thread(Callback& callback)
            : m_handle(NULL)
            , m_callback(&callback)
        {
            m_handle = CreateThread(NULL, 0, &Thread::threadProc, this, 0, NULL);
            if (m_handle == NULL)
            {
                throw SystemError();
            }
        }
    
        ~Thread()
        {
            WaitForSingleObject(m_handle, INFINITE);
            CloseHandle(m_handle);
        }
    
    private:
        Thread(const Thread&);
        Thread& operator=(const Thread&);
    
        static DWORD WINAPI threadProc(LPVOID param)
        {
            Thread* this_ = static_cast<Thread*>(param);
            return this_->threadProc();
        }
    
        DWORD threadProc()
        {
            return m_callback->run();
        }
    
    private:
        HANDLE    m_handle;
        Callback* m_callback;
    };
    

    main.cpp

    #include "Thread.hpp"
    
    #include <iostream>
    
    #include <Windows.h>
    
    class Window
    {
    public:
        void work()
        {
            doWork();
        }
    
    protected:
        virtual void doWork()
        {
            DWORD threadId = GetCurrentThreadId();
            std::cout << "Window::doWork() is running on a separate thread (id: " << threadId << ")" << std::endl;
        }
    };
    
    class SpecialWindow
        : public Window
    {
    protected:
        virtual void doWork()
        {
            DWORD threadId = GetCurrentThreadId();
            std::cout << "SpecialWindow::doWork() is running on a separate thread (id: " << threadId << ")" << std::endl;
        }
    };
    
    int main()
    {
        class RunWindow
            : public Thread::Callback
        {
        public:
            virtual DWORD run()
            {
                Window window;
                window.work();
                return 0;
            }
        };
    
        class RunSpecialWindow
            : public Thread::Callback
        {
        public:
            virtual DWORD run()
            {
                SpecialWindow window;
                window.work();
                return 0;
            }
        };
    
        {
            RunWindow runWindow;
            Thread windowThread(runWindow);
        }
        {
            RunSpecialWindow runWindow;
            Thread windowThread(runWindow);
        }
    }
    

    Anstelle von CreateThread(..) sollte _beginthreadex(..) verwendet werden, da ersteres unter bestimmten Umständen Resourcen Lecks erzeugt. Siehe http://msdn.microsoft.com/en-us/library/kdzttdcb.aspx.

    Edit 1
    #include <Windows.h> wegen GetCurrentThreadId(..) hinzugefügt

    Edit 2
    Kommentar bezüglich SystemError beim Thread Konstruktor hinzugefügt



  • Hm, ein protected non-virtual Destruktor in einer Basisklasse die bereits eine virtuelle Funktion hat. Weiß nicht so recht. Eher nicht so toll. 😉



  • Warum nicht toll? Funktioniert doch.



  • virtual ist so Java/C# - besser Templates 😋



  • cooky451 schrieb:

    Hm, ein protected non-virtual Destruktor in einer Basisklasse die bereits eine virtuelle Funktion hat. Weiß nicht so recht. Eher nicht so toll. 😉

    Warum?

    http://www.gotw.ca/publications/mill18.htm
    ➡ Guideline #4



  • theta schrieb:

    Warum?

    Weil du bereits eine virtuelle Funktion hast. Der protected-Trick macht nur wirklich Sinn, wenn das nicht der Fall ist. Diese Optimierung den Destruktor nicht virtual zu machen bringt hier so gut wie nichts, aber wenn jemand von "RunWindow" ableitet in dem Glauben da würde ja eine Polymorphe Basisklasse drunter liegen, geht alles kaputt.



  • cooky451 schrieb:

    theta schrieb:

    Warum?

    Weil du bereits eine virtuelle Funktion hast. Der protected-Trick macht nur wirklich Sinn, wenn das nicht der Fall ist. Diese Optimierung den Destruktor nicht virtual zu machen bringt hier so gut wie nichts, aber wenn jemand von "RunWindow" ableitet in dem Glauben da würde ja eine Polymorphe Basisklasse drunter liegen, geht alles kaputt.

    Naja, als Trick würde ich das nicht bezeichnen und auch nicht als Optimierung. Der Sinn ist einzig und allein, dass nicht delete auf ein Thread::Callback* aufgerufen werden kann. Wenn das benötigt werden würde, kann man den Destructor von Thread::Callback einfach public virtual machen.

    Edit

    ...aber wenn jemand von "RunWindow" ableitet in dem Glauben da würde ja eine Polymorphe Basisklasse drunter liegen, geht alles kaputt.

    Was geht kaputt?



  • theta schrieb:

    Was geht kaputt?

    RunWindow* rw = new SecondRunWindow;
    delete rw; // UB, da Destruktor nicht virtual.
    

    theta schrieb:

    Naja, als Trick würde ich das nicht bezeichnen und auch nicht als Optimierung. Der Sinn ist einzig und allein, dass nicht delete auf ein Thread::Callback* aufgerufen werden kann. Wenn das benötigt werden würde, kann man den Destructor von Thread::Callback einfach public virtual machen.

    Nein, der Sinn davon ist eher, eine Klasse komplett "non-virtual" zu halten, was hier ja gar nicht geht, weil sie schon eine virtuelle Funktion hat. Das was du beschreibst könnte man auch mit einem protected virtual Destructor erreichen. (Auch wenn das wenig sinnvoll wäre.)



  • cooky451 schrieb:

    theta schrieb:

    Was geht kaputt?

    RunWindow* rw = new SecondRunWindow;
    delete rw; // UB, da Destruktor nicht virtual.
    

    theta schrieb:

    Naja, als Trick würde ich das nicht bezeichnen und auch nicht als Optimierung. Der Sinn ist einzig und allein, dass nicht delete auf ein Thread::Callback* aufgerufen werden kann. Wenn das benötigt werden würde, kann man den Destructor von Thread::Callback einfach public virtual machen.

    Nein, der Sinn davon ist eher, eine Klasse komplett "non-virtual" zu halten, was hier ja gar nicht geht, weil sie schon eine virtuelle Funktion hat. Das was du beschreibst könnte man auch mit einem protected virtual Destructor erreichen. (Auch wenn das wenig sinnvoll wäre.)

    Was ist SecondRunWindow ?


Log in to reply