2016-03-21 3 views
3

Dieses Problem scheint zu Threads verwandt zu sein und das Programm sehr schnell auszuführen. Ich habe 2 Klassen, ThreadParent und ThreadChild, wo man von einem anderen erbt. ThreadParent erstellt einen Thread und führt die Funktion func aus, die als statisch deklariert ist, um Zeigerprobleme zu umgehen. Ich möchte jedoch, dass die geerbten Klassen wie ThreadChild entscheiden, was der Thread genau tut, so ruft func die virtuelle Funktion Do.Virtuelle Funktion von Kind Klasse ruft Elternfunktion einmal

Wenn Sie jedoch ein ThreadChild Objekt erstellen und sofort den Thread ausführen, wird am Anfang ThreadParent::Do aufgerufen, und alle folgenden Aufrufe sind ThreadChild::Do. Interessanterweise, wenn ich ein bisschen warte, bevor ich Do anrufe, passiert es nicht.

Gibt es eine bessere Lösung als ein bisschen zu warten? Und noch wichtiger, warum passiert das?


Hier ist ein kleines, aber vollständiges Beispiel. Es erstellt ein ThreadChild Objekt, das alle 200ms Do ausführt. Das Programm endet nach 1s (Warten auf eine Eingabe drücken).

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

// The parent class 
class ThreadParent { 
protected: 
    bool _loopThread; //setting this to false should end the thread 
    std::thread _thread; //the thread 

public: 
    // Basic constructor 
    ThreadParent(ThreadParent* child) 
     : _loopThread(true), 
     _thread(func, child, &_loopThread) {} 

    // Stops the thread and waits for it to finish 
    void StopThread() { 
     _loopThread = false; 
     _thread.join(); 
    } 

protected: 
    // The function the thread will be running (static because of pointer issues) 
    static void func(ThreadParent* child, bool* loopThread) { 
     //Sleep(10); //<- uncomment to solve the problem? 
     while (*loopThread) { 
      child->Do(); // Do is called every 200ms 
      Sleep(200); 
     } 
    } 
    // The function which is called repeatedly until _loopThread is set to false 
    virtual void Do() { 
     std::cout << "Parent call\n"; 
    } 
}; 

// The child class 
class ThreadChild : public ThreadParent { 
public: 
    // Basic constructor 
    ThreadChild() 
     : ThreadParent(this) {} 

protected: 
    // Redefines Do() with another message 
    void Do() { 
     std::cout << "Child call\n"; 
    } 
}; 

// The program 
int main() { 
    ThreadChild thread; // Create and run the thread 
    Sleep(1000);   // Run the thread for 1s 
    thread.StopThread(); // End it 
    std::cout << "Press <enter> to terminate..."; 
    std::cin.get(); // Wait for user to end program 
    return 0; 
} 

Output:

Parent call 
Child call 
Child call 
Child call 
Child call 
Press <enter> to terminate... 

Antwort

3

Beim Aufbau wird die Basisklasse Subobjekt vor der abgeleiteten Klasse konstruiert. Innerhalb des Basisklassenkörpers ist der dynamische Typ tatsächlich der der Basisklasse, so dass dynamische Funktionsaufrufe (virtuelle Funktionsaufrufe) die entsprechende Funktion der Basisklasse aufrufen. Je nach Zeitpunkt sehen Sie also, dass eine Funktion aufgerufen wird.

Um das zu beheben, starten Sie den Thread explizit in einer zweiten Initialisierungsfunktion, die aufgerufen wird, nachdem die Konstruktion abgeschlossen ist.

BTW: Die static Funktion ist ein Red Hering, vermeiden Sie keine Fehler. Außerdem ist es normalerweise eine schlechte Idee, Thread-Hierarchien zu erstellen. Ihre Klasseninstanzen stellen vielmehr Aufgaben oder Jobs dar, die in einem separaten Thread ausgeführt werden können oder auch nicht. Es mag eine schlechte Idee sein, diese Objekte eng an einen Thread zu koppeln. Außerdem scheint die Art, wie Sie einen Zeiger auf den Basisklassenkonstruktor übergeben, fragil, weil dadurch eine Abhängigkeit entsteht, die eigentlich gar nicht existieren sollte.

0

Wenn die abgeleitete Klasse instanziiert wird, wird zuerst der Basisklassenkonstruktor aufgerufen und der vtable Zeiger vptr wird mit der Basisklasse vtable initialisiert, die einen Zeiger auf ThreadParent::Do enthält. Nur wenn der abgeleitete Klassenkonstruktor ausgeführt wird, wird der vtable-Zeiger vptr in die dervied-Klasse vtable überschrieben, die einen Zeiger auf ThreadChild::Do enthält.

Als Ergebnis ruft das Aufrufen einer virtuellen Methode aus dem Basisklassenkonstruktor immer die Basisklassenimplementierung und nicht die abgeleitete Klassenüberlagerungsmethode auf.

This faq explains the same in greater detail

Verwandte Themen