【问题标题】:Passing a shared_ptr through a C interface that takes void *通过需要 void * 的 C 接口传递 shared_ptr
【发布时间】:2015-06-26 00:36:57
【问题描述】:

我有一个使用 SDL 的 C++ 项目,尤其是 SDL 事件。我想将事件系统用于传入的网络消息,就像它用于 UI 事件一样。我可以定义一个新的事件类型并附加一些任意数据(参见this example)。如果我使用普通指针,我会这样做:

Uint32 message_event_type = SDL_RegisterEvents(1);

/* In the main event loop */
while (SDL_Poll(&evt)) {
    if (evt.type == message_event_type) {
         Message *msg = evt.user.data1;
         handle_message(msg);
    }
}

/* Networking code, possibly in another thread */
Message *msg = read_message_from_network();
SDL_Event evt;
evt.type = message_event_type;
evt.user.data1 = msg;
SDL_PostEvent(evt);

到目前为止,我一直在使用shared_ptr<Message>。消息在构造后是只读对象,在处理时可能会在很多地方使用,所以我想为它们使用 shared_ptr。

我想在网络端和事件处理端对消息使用 shared_ptr。如果我这样做:

// in networking code:
shared_ptr<Message> msg = ...
evt.user.data1 = msg.get();

// later, in event handling:
shared_ptr<Message> msg(evt.user.data1);

那么有两个独立的 shared_ptrs 并且任何一个都可以删除 Message 对象,而其中一个仍在使用它。我需要以某种方式通过 SDL_UserEvent 结构传递 shared_ptr,该结构只有几个 void * 和 int 字段。

附加。注意SDL_PostEvent 立即返回;事件本身被放入队列中。在消息的 shared_ptr 超出网络代码的范围之后,处理程序可能会从队列中弹出事件。所以我不能传递本地 shared_ptr 的地址来复制。到复制发生时,它可能不再有效。

有没有人遇到过类似的问题并知道一个好的解决方案?

【问题讨论】:

  • 为什么是 shared_ptr,为什么不让另一个类创建和删除它们并在需要时将它们作为参考传递?
  • 传递一个指向shared_ptr的指针,并在接收函数中复制它的值。例如。 evt.user.data1 = &amp;msg;shared_ptr&lt;Message&gt; msg = *reinterpret_cast&lt;shared_ptr&lt;Message&gt;*&gt;(evt.user.data1);.
  • @JonathanPotter:对于在事件发布功能返回之前进行复制的情况,这似乎是一个很好的解决方案。但它不能。该事件必须存在于队列中,当它被弹出时,原来的 shared_ptr 很可能已经消失了。
  • @Edmund:听起来你需要推出自己的引用计数系统,而不是依赖shared_ptr 对我来说(尽管如果引用计数可以按照@EyasSH 下面的建议手动增加,那么 shared_from_this 可以是一种选择)

标签: c++ sdl shared-ptr


【解决方案1】:

new分配一个指向共享ptr的指针。这会调用构造函数(增加引用计数),但不会调用相应的析构函数,因此 shared_ptr 永远不会破坏它的共享内存。

然后在相应的处理程序中,只需在复制 shared_ptr 后销毁对象,使其引用计数恢复正常。

这与通过消息队列传递任何其他非原始类型的方式相同。

typedef shared_ptr<Message> MessagePtr;

Uint32 message_event_type = SDL_RegisterEvents(1);

/* In the main event loop */
while (SDL_Poll(&evt)) {
    if (evt.type == message_event_type) {
         // Might need to cast data1 to (shared_ptr<Message> *)
         unique_ptr<MessagePtr> data (evt.user.data1);
         MessagePtr msg = *data;
         handle_message(msg);
    }
}

/* Networking code, possibly in another thread */
MessagePtr msg = read_message_from_network();
SDL_Event evt;
evt.type = message_event_type;
evt.user.data1 = new MessagePtr (msg); 
SDL_PostEvent(evt);

消息在构造后是只读对象

我只想指出,这对多线程的安全来说是很好的,甚至是必要的。您可能想使用shared_ptr&lt;const Message&gt;

【讨论】:

  • 我一直在试图弄清楚它是如何工作的。我不明白的是,使用“new”分配的 shared_ptr 对象是如何被释放的?
  • unique_ptr 在 if 语句的末尾释放它。或者(也许更清楚?)我可以改为使用原始指针并在 if 语句的末尾调用delete data。我发现依靠unique_ptr 编写正确的代码比delete 更容易。
  • 谢谢,现在很清楚了。但是我的应用程序要求多个实体有机会处理相同的事件,并且我不希望在第一个处理程序之后删除有效负载。如果我将unique_ptr 更改为shared_ptr,一旦它们全部超出范围,是否会解除分配?
  • 我对您在这里到底需要什么感到困惑。如果没有更多细节,我认为这个问题无法回答,可能是以 SO 问题的形式。通常,如果您希望 5 个实体处理 Message,那么您可以为每个实体将 MessagePtr 放入队列一次,或者为每个实体设置不同的队列并将 MessagePtr 放入每个队列中。 OP 需要将 shared_ptr 放入队列中对我来说非常不寻常,但在队列中粘贴多个 shared_ptr 副本是证明这种选择合理的一种情况。
【解决方案2】:

似乎是使用std::enable_shared_from_this的理想场所

struct Message: std::enable_shared_from_this<Message>
{
    …
};

evt.user.data1 = msg.get();

// this msg uses the same refcount as msg above
shared_ptr<Message> msg = evt.user.data1.shared_from_this();

【讨论】:

  • 这仅适用于 C++ 11 及更高版本,但它是一种优雅且可维护的解决方案。
  • @qexyn std::shared_ptr C++11及以上
  • 出于某种原因,我在想 boost::shared_ptr。实际上 OP 没有指定命名空间,但可能是 std,在这种情况下,您的 C++11 解决方案非常适合。
  • 您可能还希望在传递到 C-land 时手动增加引用计数并在退出时减少它。我认为这在 shared_ptr 用作侵入式指针时是可能的。在提升侵入性指针中当然可能。
  • 好吧,终于知道这是怎么回事了。当第一个 msg 指针在将其放入队列后超出范围时,是什么阻止消息被销毁?
【解决方案3】:

我想到了另一种可能的技术:使用placement new 将shared_ptr 存储在与C 结构相同的空间中:

SDL_Event evt;
evt.type = event_type;
// create new shared_ptr, in the same memory as evt.user.code
new (&evt.user.code) shared_ptr<Message>(msg);
SDL_PushEvent(&evt);

SDL 然后将事件复制为 C 对象,直到后面的代码从事件中提取消息:

shared_ptr<Message> get_message(SDL_Event& evt) {
    // copy shared_ptr out of evt
    shared_ptr<Message> msg = *reinterpret_cast<shared_ptr<Message> *>(&evt.user.code);
    // destroy shared_ptr inside the event struct
    (reinterpret_cast<shared_ptr<Message> *>(&evt.user.code))->~shared_ptr();
    return msg;
}

事件结构中有几个字段应该为 shared_ptr 提供足够的空间(请参阅https://github.com/spurious/SDL-mirror/blob/master/include/SDL_events.h#L485)。

我知道这有点老套。我会很感激对该技术进行一些健全性检查。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-05-25
    • 1970-01-01
    • 2020-12-19
    • 2011-06-28
    • 2018-02-14
    • 2016-12-18
    • 2017-01-17
    • 1970-01-01
    相关资源
    最近更新 更多