微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

在析构派生类之前清理基类的资源

如何解决在析构派生类之前清理基类的资源

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

#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();
}

如果我运行代码,我会得到以下输出

/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)

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

    virtual ~TestRunner() {
        stop();
    };

使用这个析构函数一切都按预期工作。但是我不想在派生类中关心调用 stop() 方法,所有线程处理都应在 Runnable 中涵盖。你知道如何解决这个问题吗?

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

解决方法

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

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

#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 类型)。

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() 是否需要终止。

#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; 
};

创作只是略有不同。

int main() {
    Runnable<TestRunner> testRunner(1024U);
    testRunner.run();
}
,

一种方法附带您对 std::shared_ptr<TestRunner> 的使用。我不太喜欢它,但它有效(直到您决定摆脱共享指针)。

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;
  }
  ...

这保证您的 Runnable 对象不会在 run_thread 执行时被破坏。

,

问题是您的 main() 方法在 TestRunner 可以在另一个线程中执行之前返回(因此您的 run_thread() 对象被删除)。因此,当 run_thread() does 在另一个线程中执行时,它是在已经部分销毁的对象上调用的(即 ~TestRunner() 已被主线程调用),调用未定义的行为,所以不好的事情发生。

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

能够依靠 stop() 中的 ~Runnable() 调用自动为您处理它会很好,但是当 ~Runnable() 执行时,已经太晚了,您的对象的子类层已经被销毁,并且您的线程已经在尝试使用损坏的对象。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。