2016-03-02 80 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