Passing a shared_ptr through a C interface that takes void *

╄→гoц情女王★ 提交于 2019-12-07 03:52:19

问题


I have a C++ project that uses SDL, in particular SDL Events. I would like to use the event system for incoming network messages just as it is used for UI events. I can define a new event type and attach some arbitrary data (see this example). This is what I would do if I was using ordinary pointers:

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);

Instead, I've been using shared_ptr<Message> up to now. Messages are read-only objects once constructed, and might be used in lots of places while being handled, so I thought to use shared_ptr for them.

I would like to use a shared_ptr to the message in the network side, and also on the event handling side. If I do this:

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

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

then there are two independent shared_ptrs and either one could delete the Message object while the either is still using it. I would need to somehow pass the shared_ptr through the SDL_UserEvent struct, which only has a couple of void * and int fields.

Additional. Note that SDL_PostEvent returns immediately; the event itself is put on a queue. The event may be popped from the queue by the handler well after a shared_ptr to the message has gone out of scope in the networking code. So I can't pass the address of a local shared_ptr to copy from. By the time the copying occurs, it's likely to no longer be valid.

Has anyone faced a similar problem and knows of a nice solution?


回答1:


Allocate a pointer to the shared ptr with new. This calls the constructor (incrementing the reference count), but the corresponding destructor is not called so the shared_ptr will never destruct it's shared memory.

Then in the corresponding handler, just destroy the object after copying the shared_ptr bringing its reference count back to normal.

This is identical to how you'd pass any other non-primitive type through a message queue.

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);

Messages are read-only objects once constructed

I just want to point out, this is good and even necessary for the multithreading to be safe. You might want to use shared_ptr<const Message>




回答2:


Seems like ideal place to use 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();



回答3:


I thought of another potential technique: using placement new to store the shared_ptr in the same space as the C struct:

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 then copies the event around as a C object, until later code extracts the message from the event:

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;
}

There are several fields inside the event struct that should be enough space for the shared_ptr (see https://github.com/spurious/SDL-mirror/blob/master/include/SDL_events.h#L485).

I'm aware that this is a bit hacky. I'd appreciate some sanity checking of the technique.



来源:https://stackoverflow.com/questions/31063055/passing-a-shared-ptr-through-a-c-interface-that-takes-void

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!