2011-05-10 77 views
14

我試圖從C++ 11使用std ::線程。如果在執行其某個函數成員的類中可能有std :: thread,我找不到任何地方。考慮下面的例子... 在我的嘗試(下面)中,函數是run()。C + + 11:std ::線程內執行一個函數成員與線程初始化在構造函數

我用-std = C++ 0x標誌編譯gcc-4.4。

#ifndef RUNNABLE_H 
#define RUNNABLE_H 

#include <thread> 

class Runnable 
{ 
    public: 
     Runnable() : m_stop(false) {m_thread = std::thread(Runnable::run,this); } 
     virtual ~Runnable() { stop(); } 
     void stop() { m_stop = false; m_thread.join(); } 
    protected: 
     virtual void run() = 0; 
     bool m_stop; 
    private: 
     std::thread m_thread; 
}; 


class myThread : public Runnable{ 
protected: 
    void run() { while(!m_stop){ /* do something... */ }; } 
}; 

#endif // RUNNABLE_H 

我得到這個錯誤及其他:(同樣的錯誤使用和不使用$這個)

Runnable.h|9|error: no matching function for call to ‘std::thread::thread(<unresolved overloaded function type>, Runnable* const)’| 

當指針傳遞。

Runnable.h|9|error: ISO C++ forbids taking the address of an unqualified or parenthesized non-static member function to form a pointer to member function. Say ‘&Runnable::run’| 

回答

15

這種方法是錯誤的。

問題是,雖然對象仍在構建中,但它的類型仍然不是最派生的類型,而是正在執行的構造函數的類型。這意味着當您啓動線程時,對象仍然是Runnable,而對run()的調用可能會被調度到Runnable::run(),這是純虛擬的,並且反過來會導致未定義的行爲。

更糟糕的是,您可能會遇到錯誤的安全感,因爲在某些情況下,正在啓動的線程可能需要足夠長的時間才能完成當前線程的Runnable構造函數,然後輸入myThread對象,在這種情況下,新線程將執行正確的方法,但更改執行程序的系統(不同數量的內核,或系統負載或任何其他不相關的情況),並且程序將在生產中崩潰。

+1

這就是爲什麼需要'start()'是一個函數來完成實際的開始工作。 – 2011-05-10 21:41:25

+0

我不認爲線程花費足夠長的時間來調度會改變行爲,不是它的構造函數做一個副本(因此切片)綁定的結果(&Runnable :: run,this)? – Cubbi 2011-05-10 21:48:17

+1

@Etienne de Martel:有不同的方法,'start()'方法選項顯然是其中之一。另一種方法是boost和C++ 0x所採用的方法:將* runnable *對象從* thread *對象中分離出來,這會將操作作爲參數運行到構造函數中。這可以確保將要執行的對象完全構建。當然,如果你不強制你的方式進入破壞它的框架...... – 2011-05-10 21:50:11

13

下面是一些代碼來仔細考慮:

#ifndef RUNNABLE_H 
#define RUNNABLE_H 

#include <atomic> 
#include <thread> 

class Runnable 
{ 
public: 
    Runnable() : m_stop(), m_thread() { } 
    virtual ~Runnable() { try { stop(); } catch(...) { /*??*/ } } 

    Runnable(Runnable const&) = delete; 
    Runnable& operator =(Runnable const&) = delete; 

    void stop() { m_stop = true; m_thread.join(); } 
    void start() { m_thread = std::thread(&Runnable::run, this); } 

protected: 
    virtual void run() = 0; 
    std::atomic<bool> m_stop; 

private: 
    std::thread m_thread; 
}; 


class myThread : public Runnable 
{ 
protected: 
    void run() { while (!m_stop) { /* do something... */ }; } 
}; 

#endif // RUNNABLE_H 

一些注意事項:

  • 聲明m_stop作爲一個簡單的bool因爲你是可怕的不足;讀了記憶障礙
  • std::thread::join可以拋出這樣調用它沒有從析構try..catch是魯莽
  • std::threadstd::atomic<>是不可複製的,所以Runnable應標明,如果沒有其他原因,而不是避免C4512 VC++的警告
+3

我不認爲'm_stop'變量需要'volatile','std :: atomic '會處理多線程問題,'volatile'在這裏沒有額外的幫助。 – 2011-05-10 22:19:45

+0

@DavidRodríguez:FDIS中的第29.6.5/3段明確表示否則。如果std :: atomic <>對象不是易失性的,那麼對該對象的訪問可能會被重新排序; std :: atomic <>類型沒有什麼特別的改變了這個事實。 (由於不可編輯的錯字重新粘貼) – ildjarn 2011-05-13 01:00:47

+2

我沒有注意到前面評論中的錯字,但我認爲它比新版本更精確。標準在該段中說的是對原子對象的操作可以被合併*(並不是重新排序)。我對它的理解是'std :: atomic i = 0;對於(; i <10; i = i + 1){}'可以轉換爲'std :: atomic i = 10;'(這對於volatile變量不可行,所有讀寫操作都需要最終的可執行文件)。儘管如此,我沒有足夠的時間閱讀*(如完全摘要)標準的「原子」部分。 – 2011-05-13 07:45:21

相關問題