2016-03-02 119 views
-2

我有包含間接訪問這樣的對象,從而一個線程的對象:試圖訪問被銷燬對象

#include <iostream> 
#include <thread> 
#include <atomic> 

class A; 

class Manager 
{ 
public: 
    Manager(void) = default; 
    void StartA(void) 
    { 
     a = std::make_unique<A>(*this); 
    } 
    void StopA(void) 
    { 
     a = nullptr; 
    } 
    A& GetA(void) 
    { 
     return *a; 
    } 
private: 
    std::unique_ptr<A> a; 
}; 

class A 
{ 
public: 
    A(Manager& manager) 
     : manager{manager}, 
     shouldwork{true}, 
     thread{[&]{ this->Run(); }} 
    { 
    } 
    ~A(void) 
    { 
     shouldwork = false; 
     thread.join(); 
    } 
private: 
    Manager& manager; 
    std::atomic<bool> shouldwork; 
    std::thread thread; 
    void Run(void) 
    { 
     while (shouldwork) 
     { 
      // Here goes a lot of code which calls manager.GetA(). 
      auto& a = manager.GetA(); 
     } 
    } 
}; 

int main(int argc, char* argv[]) 
try 
{ 
    Manager man; 
    man.StartA(); 
    man.StopA(); 
} 
catch (std::exception& e) 
{ 
    std::cerr << "Exception caught: " << e.what() << '\n'; 
} 
catch (...) 
{ 
    std::cerr << "Unknown exception.\n"; 
} 

的問題是,當一個線程調用Manager::StopA並進入的A析構函數, A段落內的線程Manager::GetA。我怎樣才能解決這個問題?

+0

因爲嘗試解引用'nullptr',所以會出現分段錯誤。調用StopA會將'a'設置爲'nullptr'。 GetA取消引用'return * a' – Emil

回答

3

StopA()您設置了a = nullptr;,這反過來破壞了a對象,並且對其成員的所有進一步訪問都會導致未定義的行爲(可能導致分段錯誤)。

只需將a = nullptr;移至Manager的析構函數即可解決此問題。更好的是,當Manager的析構函數運行時(即完全移除該行代碼),允許std::unique_ptr的RAII機制銷燬對象a

隨着active objectimplementations,小心控制成員變量很重要,特別是「停止變量/控制」(這裏是shouldwork = false;)。允許管理員直接訪問該變量或通過一種方法訪問該變量,以在其銷燬之前停止活動對象。


這裏的一些代碼看起來不合適或不明顯, a = std::make_unique<A>(*this);。重新設計可以幫助簡化一些代碼。 Manager類可以被刪除。

class A 
{ 
public: 
    A(): shouldwork{true}, thread{[&]{ this->Run(); }} 
    { 
    } 
    void StopA() 
    { 
     shouldwork = false; 
     thread.join(); 
    } 
private: 
    std::atomic<bool> shouldwork; 
    std::thread thread; 
    void Run(void) 
    { 
     while (shouldwork) 
     { 
      // code... 
     } 
    } 
}; 

的代碼是沿std::thread線爲藍本,是由一種嘗試之前加入其胎面的停止更加可控。在這種情況下,析構函數留空,以模仿終結(調用std::terminate)的結果,就像標準線程庫一樣。線程必須在銷燬前明確加入(或分離)。

重新引入Manager,代碼可能如下所示;

class A 
{ 
public: 
    A() : shouldwork{true}, thread{[&]{ this->Run(); }} {} 
    void StopA() { shouldwork = false; thread.join(); } 
private: 
    void Run(); 
    std::atomic<bool> shouldwork; 
    std::thread thread; 
}; 

class Manager 
{ 
public: 
    Manager() = default; 
    void StartA(void) 
    { 
     a = std::make_unique<A>(); 
    } 
    void StopA(void) 
    { 
     a->StopA(); 
    } 
    A& GetA(void) 
    { 
     return *a; 
    } 
private: 
    std::unique_ptr<A> a; 
}; 

void A::Run() 
{ 
    while (shouldwork) 
    { 
     // Here goes a lot of code which calls manager.GetA(). 
     auto& a = manager.GetA(); 
    } 
} 

而您的main保持原樣。

+0

好吧,我正在嘗試應用您的修復,不幸的是,真正的代碼有點複雜,現在我在'thread.join()'中得到異常,我會更新這個問題,如果我我需要更多的幫助。 – Lyberta

+0

@FaTony。好的,那可能是它不再是'joinable()',這意味着其他東西已經加入了它,或者它已經被分離了。注意正在移動的線程,並加入不再代表該線程的對象。 – Niall

+0

好吧,現在我仍然在'GetA'獲得segfault,而另一個線程在'A'構造函數中。 – Lyberta