如何通过unsigned long传递std::shared_ptr给回调函数

How to pass std::shared_ptr to callback through unsigned long?

本文关键字:ptr 回调 函数 shared unsigned long 传递 std 何通过      更新时间:2023-10-16

我有一个旧的c风格库,它使用unsigned long作为用户参数的回调,我想将我的shared_ptr传递给回调,以便引用计数递增。

void callback( unsigned long arg ) {
  std::shared_ptr<my_class> ptr = ??? arg ???
}
void starter_function() {
  std::shared_ptr<my_class> ptr = std::make_shared<my_class>();
  unsigned long arg = ??? ptr ???
  // pass to library so it may be used by callback
}

目前我在shared_ptr上使用get(),然后使用c风格的强制转换,但是当start_function超出作用域时,这会产生问题。

创建一个静态存储(可以基于std::map<unsigned long, std::shared_ptr<T>>)。提供函数给:

  • 在store中注册一个共享指针(将其保存在map中,并返回一个唯一的long)。
  • 检索特定unsigned long
  • 对应的shared_ptr
  • 删除特定的unsigned long

(后两个函数可以合并)。

这样做的好处是不需要在指针和unsigned long之间进行任何危险的强制转换,而且(如果返回值基于一个递增计数器,您可以测试其唯一性),可以更容易地发现一个对象被创建、删除,然后在同一地址创建另一个对象的问题。

这是一个想法的草图:(特别注意,它不是线程安全的!)

template typename<T>
class Store
{
    static std::map<unsigned long, std::shared_ptr<T>> store;
    unsigned long h;
    bool contains(unsigned long i)
    {
        return store.find(i) != store.end();
    }
 public:
    unsigned long save(const std::shared_ptr& ptr)
    {
        if (store.size() >= std::numeric_limits<unsigned long>::max())
        {
            // Handle error.  Only possible if 64 bit program with
            // 32 bit unsigned long.
        }
        // find an unused handle.
        do
        {
            ++h;
        } 
        while(contains(h));  // Not a good approach if there are main long-lived objects, and h might wrap.
        // Store and return handle.
        store[h] = ptr;
        return h;
    }
    std::shared_ptr<T> retrieve(unsigned long handle)
    {
       if (!contains(handle))
       {
           // handle error
       }
       const auto result = store[handle];
       store.erase(handle);
       return result;
    }
 };

遗憾的是,这个问题没有"goto"解决方案。我有过几次这样的情况,并根据具体情况以不同的方式解决了这个问题。


一个"简单"的解决方案是(如果你有一些类)将指针作为类成员存储在容器中,以确保它们保持活动状态。然后在函数内部使用原始指针。但是你必须确保当你的对象被销毁时回调函数不会被调用(所以将回调函数与对象一起存储)。只有当不从容器中删除任何对象时,这才能顺利地工作。


对于线程,通过引用传递共享的ptr,然后复制它

{
    auto sP = make_shared<...>(...);
    thread_start(&sp);
    wait_for_thread_init();
}
void thread_start(std::shared_ptr<...>* ref ) 
    //or unsigned long but you can cast this to a shared_ptr<...>* later.
{
    std::shared_ptr otherPointer = *ref; //here your ref count gets increased
    send_init_complete(); 
    //never use ref from here on. only otherPointer.
}

但是c++ 11有一个自己的线程库可以更好地处理这个问题,所以当你使用线程时应该使用它。


取决于你所处的环境,你必须要有创意。最后,当您没有实际的解决方案时,您需要使用原始指针。只需添加足够的注释,说明回调应该删除这些对象等。

您有两个基本选项。这两个选项都假设在您的平台上,unsigned long足够大以容纳指针:

  1. 将指向std::shared_ptr的指针转换为unsigned long,并传递它

  2. 使用get()来检索底层指针,并且只传递它

对于第二个选项,假设你的回调不需要构造另一个shared_ptr;否则你需要使用enable_shared_from_this

另外,这两个选项都假设当回调被调用时,shared_ptr和底层对象将保持在作用域中。如果不是:

  • 使用new复制shared_ptr你将需要delete后,它不再需要,回调不再使用。
  • 如果在你的平台上,unsigned long不够大,不能容纳一个指针,它就会变得笨拙。最干净的解决方案是保留一个std::map<unsigned long, std::shared_ptr<...>>,并为每个shared_ptr分配一个唯一的unsigned long值的计数器,将shared_ptr插入映射,并将映射的键传递给回调。