如何防止截止时间计时器调用已删除类中的函数?

时间:2012-10-25 14:24:08

标签: c++ boost-asio

我在一段现实代码中遇到问题,boost::asio::deadline_timer调用属于已删除类的函数,偶尔会导致分段错误。

我遇到的问题是删除deadline_timer是从同一个io_service上的另一个计时器运行的。删除第一个deadline_timer将触发对要运行的函数的最后一次调用,并出现boost::asio::error::operation_aborted错误。但是,这只能在删除完成后在(相同的)io_service上进行调度,但到那时该对象已被删除,因此不再有效。

所以我的问题是:如何防止这种情况发生?

以下是具有相同错误的简化示例:

//============================================================================
// Name        : aTimeToKill.cpp
// Author      : Pelle
// Description : Delete an object using a timer, from a timer
//============================================================================

#include <iostream>
#include <boost/function.hpp>
#include <boost/bind.hpp>
#include <boost/asio.hpp>
#include <boost/thread.hpp>

using namespace std;
using namespace boost;

struct TimeBomb
{
    bool m_active;
    asio::deadline_timer m_runTimer;

    TimeBomb(boost::asio::io_service& ioService)
    : m_active(true)
    , m_runTimer(ioService)
    {
        cout << "Bomb placed @"<< hex << (int)this << endl;
        m_runTimer.expires_from_now(boost::posix_time::millisec(1000));
        m_runTimer.async_wait(boost::bind(&TimeBomb::executeStepFunction, this, _1));
    }

    ~TimeBomb()
    {
        m_active = false;
        m_runTimer.cancel();
        cout << "Bomb defused @"<< hex << (int)this << endl;
    }

    void executeStepFunction(const boost::system::error_code& error)
    {
        // Canceled timer
        if (error ==  boost::asio::error::operation_aborted)
        {
            std::cout << "Timer aborted: " << error.message() << " @" << std::hex << (int)this << std::endl;
            return;
        }
        if (m_active)
        {
            // Schedule next step
            cout << "tick .." <<endl;
            m_runTimer.expires_from_now(
                    boost::posix_time::millisec(1000));
            m_runTimer.async_wait(boost::bind(&TimeBomb::executeStepFunction, this, _1));
        }
    }
};

struct BomberMan
{
    asio::deadline_timer m_selfDestructTimer;
    TimeBomb* myBomb;

    BomberMan(boost::asio::io_service& ioService)
    : m_selfDestructTimer(ioService)
    {
        cout << "BomberMan ready " << endl;
        myBomb = new TimeBomb(ioService);
        m_selfDestructTimer.expires_from_now(boost::posix_time::millisec(10500));
        m_selfDestructTimer.async_wait(boost::bind(&BomberMan::defuseBomb, this, _1));
    }

    void defuseBomb(const boost::system::error_code& error)
    {
        cout << "Defusing TimeBomb" << endl;
        delete myBomb;
    }
};


int main()
{
    boost::asio::io_service m_ioService;
    BomberMan* b = new BomberMan(m_ioService);
    m_ioService.run();
    return 0;
}


./aTimeToKill
BomberMan ready 
Bomb placed @9c27198
tick ..
tick ..
tick ..
tick ..
tick ..
tick ..
tick ..
tick ..
tick ..
tick ..
Defusing TimeBomb
Bomb defused @9c27198
Timer aborted: Operation canceled @9c27198

删除后会打印最后一行,说明我的问题。

4 个答案:

答案 0 :(得分:6)

解决此问题的典型方法是使用shared_ptr

#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/shared_ptr.hpp>

#include <iostream>

using namespace std;

struct TimeBomb : public boost::enable_shared_from_this<TimeBomb>
{
    bool m_active;
    boost::asio::deadline_timer m_runTimer;

    TimeBomb(boost::asio::io_service& ioService)
    : m_active(true)
    , m_runTimer(ioService)
    {
        cout << "Bomb placed @"<< hex << this << endl;
        m_runTimer.expires_from_now(boost::posix_time::millisec(1000));
    }

    void start()
    {
        m_runTimer.async_wait(boost::bind(&TimeBomb::executeStepFunction, shared_from_this(), _1));
    }

    void stop()
    {
        m_runTimer.cancel();
    }

    ~TimeBomb()
    {
        m_active = false;
        m_runTimer.cancel();
        cout << "Bomb defused @"<< hex << this << endl;
    }

    void executeStepFunction(const boost::system::error_code& error)
    {
        // Canceled timer
        if (error ==  boost::asio::error::operation_aborted)
        {
            std::cout << "Timer aborted: " << error.message() << " @" << std::hex << this << std::endl;
            return;
        }
        if (m_active)
        {
            // Schedule next step
            cout << "tick .." <<endl;
            m_runTimer.expires_from_now(
                    boost::posix_time::millisec(1000));
            m_runTimer.async_wait(boost::bind(&TimeBomb::executeStepFunction, shared_from_this(), _1));
        }
    }
};

struct BomberMan
{
    boost::asio::deadline_timer m_selfDestructTimer;
    boost::shared_ptr<TimeBomb> myBomb;

    BomberMan(boost::asio::io_service& ioService)
    : m_selfDestructTimer(ioService)
    {
        cout << "BomberMan ready " << endl;
        myBomb.reset( new TimeBomb(ioService) );
        myBomb->start();
        m_selfDestructTimer.expires_from_now(boost::posix_time::millisec(10500));
        m_selfDestructTimer.async_wait(boost::bind(&BomberMan::defuseBomb, this, _1));
    }

    void defuseBomb(const boost::system::error_code& error)
    {
        cout << "Defusing TimeBomb" << endl;
        myBomb->stop();
    }
};


int main()
{
    boost::asio::io_service m_ioService;
    BomberMan* b = new BomberMan(m_ioService);
    m_ioService.run();
    return 0;
}

答案 1 :(得分:2)

这就是boost::shared_ptrboost::enable_shared_from_this的原因。从TimeBomb继承boost::enable_shared_from_this类,如下所示:

struct TimeBomb : public boost::enable_shared_from_this< TimeBomb >
{
...
}

实例化共享ptr而不是裸ptr:

boost::shared_ptr< TimeBomb > myBomb;
...
myBomb.reset( new TimeBomb(ioService) );

最后在TimeBomb使用shared_from_this()而不是this来构建处理程序。

m_runTimer.async_wait( boost::bind( &TimeBomb::executeStepFunction, shared_from_this(), _1));

当然TimeBomb类应该公开cancel方法,通过该方法取消异步操作,而不是删除,或者在这种情况下,重置shared_ptr。

答案 2 :(得分:0)

来自Sam Miller的shared_ptr答案之所以有效,是因为使用shared_ptr可以让TimeBomb在BomberMan的生命周期内保持不变。这对你来说可能没问题,也可能没有。

建议更完整的解决方案是从工厂获取TimeBomb实例,然后在完成时将它们释放回来,而不是新建和删除它们(将它们作为标准指针,而不是shared_ptrs,因为你没有&# 39;即使你正在控制生命周期,也要拥有它们。工厂可以让它们一直闲置直到它们被取消,然后为你删除它们。保持Sam Miller的stop()功能不变。

要实现此目的,请从

行的接口派生工厂
class ITimeBombObserver 
{
public:
    virtual void AllOperationsComplete(TimeBomb& TmBmb)=0;
};

将工厂作为ITimeBombObserver传递给每个TimeBomb,并取消TimeBomb调用此函数。工厂可以清理&#34;使用&#34;每次创建或发布一个TimeBombs,或者使用计划清理或其他一些方法,最适合您的应用程序。

使用这种方法你的BomberMan甚至不需要在defuseBomb()中显式释放TimeBomb,如果它不想要,对stop()的调用可以自动释放(尽管在这种情况下你仍然应该将指针归零,因为此时它变得无效。)这是否是一个好主意取决于你的真实问题,所以我会留给你决定。

答案 3 :(得分:0)

对于一个非常简单的修复,这个怎么样? (我只包括你需要改变的部分)

它的工作原理是因为您只能在取消定时器时访问堆栈变量。当然,你根本不需要在析构函数中回调处理程序,但我假设你的真实代码无论出于何种原因都需要这个。

~TimeBomb()
{
    m_active = false;
    executeStepFunction(boost::asio::error::interrupted);
    m_runTimer.cancel();
    cout << "Bomb defused @"<< hex << (int)this << endl;
}

void executeStepFunction(const boost::system::error_code& error)
{
    // Canceled timer
    if (error ==  boost::asio::error::operation_aborted)
    {
        return;
    }
    if (error ==  boost::asio::error::interrupted)
    {
        std::cout << "Timer aborted: " << error.message() << " @" << std::hex << (int)this << std::endl;
        return;
    }
...