如何通过无符号长整型(unsigned long)将std::shared_ptr传递给回调函数?

4

我有一个旧的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超出作用域时会出现问题。


正如您所看到的答案,有很多想法,但每个想法都有优点和缺点。在这里,您需要了解对您的项目来说什么是重要的。希望其中一种“好”的解决方案能够帮助您。 - Hayt
3个回答

9
创建一个静态存储器(可以基于std::map<unsigned long, std::shared_ptr<T>>)。提供以下功能:
  • 在存储器中注册一个共享指针(将其保存在映射中,并返回唯一的长整型值)。
  • 检索与特定无符号长整型值对应的shared_ptr
  • 删除特定的无符号长整型值。
(最后两个函数可以合并。)
这种方法的优点是不需要在指针和无符号长整型之间进行任何不安全的转换,而且(如果返回值基于递增计数器,您可以测试其唯一性),使得更容易发现一个对象被创建、删除,然后另一个对象在同一地址处被创建的问题。
以下是该想法的草图:(请注意,它不是线程安全的!)
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;
    }
 };

这也是一个不错的解决方案。在执行期间依赖RAII时,它会有一些问题,因为您要么必须手动删除对象,要么它们只能在二进制文件结束时被销毁。但是,如果您不需要依赖释放不需要的内存,那就很好。 - Hayt
不,如果您从存储中删除了shared_ptr(如示例代码中所示),那么它们只会在回调完成后才继续存在。 - Martin Bonner supports Monica
啊,是的。当你两次使用相同的“id”时,会遇到问题。比如注册一个共享指针一次并将“id”分配给2个回调函数。我的意思是,你可以“捕获”错误,但这只是让用户意识到避免这种情况的一些提示。正如我所说的那样,这是一个不错的解决方案。:) 下次再遇到这个问题时,我会记住这个方法的。 - Hayt
好的。可能最好的解决方案是为每个回调函数分配一个独特的ID。否则,您需要将“检索”和“从存储中删除”功能分开(并小心地调用后者)。 - Martin Bonner supports Monica
1
我更喜欢一次性检索和删除。因为如果您不知道哪个回调将是最后一个,那么您就无法进行删除。 - Hayt

1
你有两个基本选项。这两个选项都假定在您的平台上,一个unsigned long足够大,可以容纳一个指针:
  1. 将指针转换为std::shared_ptrunsigned long,并传递它。
  2. 使用get()检索底层指针,然后只需传递它。
对于第二个选项,假定您的回调不需要构造另一个shared_ptr;否则,您将需要使用enable_shared_from_this
此外,这两个选项都假定shared_ptr和底层对象在回调被调用时仍然处于作用域内。如果不是:
  1. 使用new复制shared_ptr。在不再需要它且回调不再使用时,您需要delete它。
如果在您的平台上,unsigned long 不足以容纳指针,那么这会变得更加笨拙。最清晰的解决方案是保留一个 std::map<unsigned long, std::shared_ptr<...>>,以及一个计数器为每个 shared_ptr 分配唯一的 unsigned long 值,将 shared_ptr 插入到 map 中,并将 map 的键传递给回调函数。

1
复制对象可能不是所需的,因为他想在同一个对象上工作。因此,如果人们依赖于希望在同一实例上具有多个回调,则必须意识到这一点。 - Hayt

1

很遗憾,这个问题没有“goto”解决方案。我曾经遇到过几次,根据情况采取不同的解决方法。


如果你有一些类的话,一个“简单”的解决方案是将指针作为类成员存储在容器中,确保它们被保持活动状态。然后在函数内部使用原始指针。但要确保当你的对象被销毁时,回调函数不会被调用(因此将回调函数与对象一起存储)。但如果你不从容器中移除任何对象,则只有添加操作可以顺利工作。
使用线程时,您需要通过引用传递共享指针,然后进行复制。
{
    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有自己的线程库,处理这个问题更好,因此在使用线程时应该使用它。


根据您的环境,您需要在此处进行创意。最终,当您没有实际解决方案时,您需要使用原始指针。只需添加足够的评论,使回调函数应删除这些对象等。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接