首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >在销毁派生类之前清理基类的资源

在销毁派生类之前清理基类的资源
EN

Stack Overflow用户
提问于 2021-07-08 05:38:23
回答 3查看 202关注 0票数 2

我实现了一个自己的Runnable来调用线程:

代码语言:javascript
复制
#include <iostream>
#include <memory>
#include <thread>

class Runnable {
public:
    Runnable(): running_thread_(nullptr) {}

    void run() {
        if(running_thread_)
            return;

        running_thread_ = std::unique_ptr<std::thread>(new std::thread(&Runnable::run_thread, this));

    }

    virtual ~Runnable() {
        stop();
    }

    void stop() {
        if (running_thread_ && running_thread_->joinable()) {
            running_thread_->join();
        }
        running_thread_ = nullptr;
    }

protected:
    void run_thread()
    {
        std::cout << "a" << std::endl;
        start();
        std::cout << "b" << std::endl;
    }

    virtual bool start() = 0;

private:
    std::unique_ptr<std::thread> running_thread_;
};

class TestRunner : public Runnable {
public:
    virtual ~TestRunner() {
    };

protected:
    bool start() override {
        return true;
    }
};

int main() {
    auto testRunner = std::make_shared<TestRunner>();
    testRunner->run();
}

如果运行代码,将得到以下输出:

代码语言:javascript
复制
/home/vagrant/tmp/clionTestProject/cmake-build-debug/clionTestProject 
a 
pure virtual method called 
terminate called without an active exception

Process finished with exit code 134 (interrupted by signal 6: SIGABRT)

看起来,start()是在TestRunner的析构函数之后调用的。对Runnable()的析构函数的stop()调用没有帮助,因为TestRunner的析构函数是在之前执行的。至少我发现可以通过在TestRunner的析构函数中调用stop来修复它:

代码语言:javascript
复制
    virtual ~TestRunner() {
        stop();
    };

使用这个析构函数,一切都如预期的那样工作。但是,我不想在派生类中调用stop()方法,所有的线程处理都将在Runnable中完成。你知道怎么解决这个问题吗?

那么,我要搜索的是:有什么方法可以防止在线程完成之前调用TestRunner的析构函数吗?

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2021-07-08 10:10:41

另一种选择可以是不让TestRunner继承Runnable,而是让Runnable拥有一个TestRunner成员变量。这完全避免了这个问题。

我在这里删除了智能指针,因为我认为它们只是从派生类对象可能在线程脚下被破坏这一实际问题中获得了关注。也就是说,在调用virtual成员函数之前,以及在线程运行并处理派生类中的成员变量时。

代码语言:javascript
复制
#include <atomic>
#include <iostream>
#include <thread>
#include <type_traits>
#include <utility>

// A base class for TestRunner
class RunnerBase {
public:
    void stop() { terminate_ = true; }
    bool terminated() const { return terminate_; }

    virtual void operator()() = 0;

private:
    std::atomic<bool> terminate_ = false;
};

Runnable构造函数现在将其参数传递给成员变量(在本例中为TestRunner类型)。

代码语言:javascript
复制
template<class T>
class Runnable final {
public:
    static_assert(std::is_base_of_v<RunnerBase, T>, "T must inherit from RunnerBase");

    template<class... Args>     // constructor forwarding to the runner
    Runnable(Args&&... args) : runner{std::forward<Args>(args)...} {}

    void run() {
        if(running_thread_.joinable()) return;
        running_thread_ = std::thread(&Runnable::run_thread, this);
    }

    ~Runnable() { stop(); }

    void stop() {        
        if (running_thread_.joinable()) {
            runner.stop();
            running_thread_.join();
        }
    }

protected:
    void run_thread() {
        std::cout << "a" << std::endl;
        runner();
        std::cout << "b" << std::endl;
    }

private:
    std::thread running_thread_;
    T runner;
};

TestRunner继承自RunnerBase,如果是终止时间,可以检查terminated()

代码语言:javascript
复制
#include <vector>

class TestRunner : public RunnerBase {
public:
    TestRunner(size_t data_size) : foo(data_size) {}

    void operator()() override {
        while(!terminated()) {
            // work on data that only exists in the derived class:
            for(auto& v : foo) ++v;            
            std::cout << '.';
        }
    }

private:    
    // Some data that the thread works on that wóuld get destroyed before
    // the base class destructor could call stop() if inheriting `Runnable`
    std::vector<int> foo; 
};

创造只是略有不同。

代码语言:javascript
复制
int main() {
    Runnable<TestRunner> testRunner(1024U);
    testRunner.run();
}
票数 2
EN

Stack Overflow用户

发布于 2021-07-08 06:53:45

一种使用std::shared_ptr<TestRunner>的方法是“猪背”。我不太喜欢它,但它可以工作(直到您决定去掉共享指针)。

代码语言:javascript
复制
class Runnable : public std::enable_shared_from_this<Runnable>{
  ...
      running_thread_ = std::unique_ptr<std::thread>(new std::thread(&Runnable::run_thread, shared_from_this()));
  ...
  static void run_thread(std::shared_ptr<Runnable> s)
  {
      std::cout << "a" << std::endl;
      s->start();
      std::cout << "b" << std::endl;
  }
  ...

这保证了在执行run_thread时不会销毁可运行的对象。

票数 1
EN

Stack Overflow用户

发布于 2021-07-08 05:45:30

问题是main()方法在其他线程执行run_thread()之前返回(因此删除了run_thread()对象)。因此,当run_thread()在另一个线程中执行时,它被调用到一个已经部分销毁的对象上(即,主线程已经调用了~TestRunner() ),调用未定义的行为就会发生糟糕的事情。

如果将testRunner->stop()添加到main()的末尾,则可以避免问题,因为这可以确保main()在派生线程退出之前不会返回(因此不会销毁TestRunner对象)。

能够依靠~Runnable()中的~Runnable()调用自动处理它是很好的,但是当~Runnable()执行时,已经太晚了,您的对象的子类层已经被销毁,并且您的线程已经试图使用一个破碎的对象。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/68296163

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档