如何正确删除指向回调函数的指针。

时间:2015-02-23 12:30:57

标签: c++ curl libcurl stdasync

  1. 我有一个MainProgram.exe,它调用MyDll.dll并使用curl接收回调函数的数据。
  2. 我在一个名为CurlGetData的函数中包裹了curl,它创建了一个curl实例并执行curl_easy_perform。
  3. 这是我的代码:

    //Interface class to derive from
    class  ICurlCallbackHandler
    {
    public:
        virtual size_t CurlDataCallback( void* pData, size_t tSize ) = 0;
    };
    
    //Class that implements interface
    class CurlCallbackHandler : public ICurlCallbackHandler
    {
    public:
        bool m_exit = false;
    
        virtual size_t CurlDataCallback( void* pData, size_t tSize ) override
        {
            if(m_exit)
                return CURL_READFUNC_ABORT;
    
           // do stuff with the curl data
    
            return tSize;
        }
    }
    
    
    CurlCallbackHandler *m_curlHandler;
    
    //Create an instance of above class in my dll constructor
    MyDll:MyDll()
    {
        m_curlHandler = new CurlCallbackHandler();
    }
    
    //Cleanup above class in my dll destructor
    MyDll:~MyDll()
    {
        delete m_curlHandler;
        m_curlHandler = nullptr;
    }
    
    //Function to start receiving data asynchronously  
    void MyDll::GetDataAsync()
    {
        std::async([=]
        {
            //This will receive data in a new thread and call CurlDataCallback above
            //This basically calls easy_perform
            CurlGetData(m_curlHandler);
        }
    }
    
    //Will cause the curl callback to return CURL_READFUNC_ABORT
    void MyDll::StopDataAsync()
    {
        m_curlHandler->m_exit = true;
    }
    

    从我的主程序调用函数GetDataAsync,它基本上调用curl_easy_perform并使用m_curlHandler作为其回调函数,该函数调用回CurlDataCallback。

    这一切都很好但是每当我的主程序退出时,它会调用MyDll :: StopDataAsync来停止curl数据回调,然后调用MyDll的析构函数来清理m_curlHandler。

    但是我发现在那一刻curl还没有完成这个回调,程序崩溃了,因为m_curlHandler已被删除,但新的异步线程中的curl回调仍在使用它。

    有时它会很好地关闭,但有时它会崩溃,因为curlcallback试图访问已经被析构函数删除的指针。

    如何最好地清理m_curlHandler?我想避免等待超时,因为这会影响我的主程序的性能。

1 个答案:

答案 0 :(得分:0)

根据C ++标准,MyDll::GetDataAsync()函数不应该立即返回,它应该阻塞直到异步线程完成,这将有效地使操作同步。但是我相信微软有意违反了std::async规范的这一部分,所以实际上它会立即返回,并且你可以在异步线程仍在使用时销毁回调(这正是问题所在)如果Microsoft实现遵循标准,则避免使用!)

解决方案是保持std::future返回std::async,然后阻止该未来(确保异步线程已完成),然后再销毁回调。

class MyDLL
{
    std::future<void> m_future;
    ...
};

MyDll:~MyDll()
{
    StopDataAsync();
    m_future.get();        // wait for async thread to exit.
    delete m_curlHandler;  // now it's safe to do this
}

//Function to start receiving data asynchronously  
void MyDll::GetDataAsync()
{
    m_future = std::async([=]
    {
        //This will receive data in a new thread and call CurlDataCallback above
        //This basically calls easy_perform
        CurlGetData(m_curlHandler);
    }
}

N.B。您的m_exit成员应该是std::atomic<bool>(或者您应该使用互斥锁来保护对它的所有读写操作),否则您的程序会出现数据争用,因此会有未定义的行为。

我还会将std::unique_ptr<CurlCallbackHandler>用于m_curlHandler

  

我想避免等待超时,因为这会影响我的主程序的性能。

上面的解决方案将导致析构函数等待,但只有在回调需要注意m_exit == true并导致异步线程停止运行时才会等待。这意味着你只需要等待所需的时间,而不是等待超时,这意味着猜测“足够长”的时间长度,然后可能会增加一些安全性。