1

I have a shared pointer of objects throughout my application. Here is one example:

std::shared_ptr<MyTexture> texture = loadImageFromFile("someFile.png");

I am using a UI library called ImGui and this library has a concept of a texture ID, which is a void pointer. I am currently passing the data to this library by doing the following:

ImGui::Image(texture.get());

And my backend stores these textures in a map that points to the Graphics API related data type (it is a Vulkan descriptor set but ) to be able to render the texture. Now, I have one particular issue about this. I want to manage this with a smart pointer. However, I am passing it as a raw pointer; so, I am not able to just wrap it in a shared pointer because the original shared pointer will be destroyed by this. Internally, I am using shared pointers everywhere except for this one particular case. Is there some way that I can pass a shared pointer object itself as raw void pointer and then cast it back to shared pointer; so, I do not lose the shared pointer counter when passing the pointer to this function?

Gasim
  • 7,615
  • 14
  • 64
  • 131
  • 5
    If the `Image` is accepting a `void*` then it must assume that it is not responsible for ownership. There is no way to meaningfully re-wrap that `void*` in a `shared_ptr` without having access to an original. – François Andrieux Dec 31 '21 at 18:39
  • `std::shared_ptr::get()` does not decrease use count, so I do not understand your concern - "because the original shared pointer will be destroyed by this." – Slava Dec 31 '21 at 18:47
  • Now, even if you could do this, how would the shared_ptr know the pointer is still in use by Image? And ask yourself why a shared_ptr? The code calling loadImageFromFile will be the owner of the texture right? If so then you should model it as a unique_ptr and manage its lifecycle explicitly – Pepijn Kramer Dec 31 '21 at 18:50
  • @Slava internally, I am using shared pointer everywhere, which means that either I have to extend my internal functions to accept raw pointer or find a way to wrap the raw pointer in a shared ptr and pass that. – Gasim Dec 31 '21 at 18:51
  • Gasim don't do that, you should use shared_ptr only by design when there is a clear need for shared ownership. If you use shared_ptr's everywhere you're bound to make a cyclic dependency at some time and then your memory can no longer be freed. https://stackoverflow.com/questions/6876751/differences-between-unique-ptr-and-shared-ptr – Pepijn Kramer Dec 31 '21 at 18:54
  • @PepijnKramer unfortunately, it is not as simple as that for me. There are multiple objects (CommandList, Descriptors, Render Graph, Materials) that reference these pointers and it is really complicated to keep track of their lifecycle when when there is no clear owner of these objects. For example, a material can get destroyed while the texture can be accessible in the command list in a frame; so, the texture should still be available. – Gasim Dec 31 '21 at 19:00
  • I have heard that using some kind of a Handle structure instead of shared pointers is better suited for 3D rendering engines but that's something that I am planning to investigate in the future while trying to work around the current problem right this moment. – Gasim Dec 31 '21 at 19:02
  • @Gasim The function `ImGui::Image(void*);` simply implies that `Image` will not manage the lifetime of the passed object, and that it is your (the caller's) problem. If it *can* accept a shared_ptr and participate in shared ownership then you need to change its parameter type. – François Andrieux Dec 31 '21 at 19:03
  • 1
    @Gasim Well that sounds like a case for shared ownership then, just be careful about the directions of ownership then to avoid those cycles. – Pepijn Kramer Dec 31 '21 at 19:07
  • 1
    What should happen if everything else drops references to the `MyTexture` and the only thing with a reference is the `Image`? If this case is impossible, then you can take the easy way out with [`std::enable_shared_from_this`](https://en.cppreference.com/w/cpp/memory/enable_shared_from_this). – HTNW Dec 31 '21 at 19:19
  • 1
    @Gasim: "*it is really complicated to keep track of their lifecycle when when there is no clear owner of these objects.*" Your problem is over-ownership. You shouldn't *want* a material's destruction to cause the destruction of a texture. There should be some higher level system that understands how your application is using GPU resources and engine resources. A texture should only be deleted when this higher-level system wants it to. And it is this system's responsibility to ensure that it won't want to if there exist any active materials still using it. – Nicol Bolas Dec 31 '21 at 19:45
  • @HTNW thank you for this. It worked as expected. – Gasim Dec 31 '21 at 22:53
  • @NicolBolas This is something I want to investigate further (e.g using Handles and having a registry that maps simple handles to device specific resources) but that's is something a bit further down my pipeline due to needing to implement other features as well. For the time being `enable_shared_from_this` solves my problem really well but it is definitely something I would try to improve as I keep developing my app. – Gasim Dec 31 '21 at 23:04

1 Answers1

2

No, not with std::shared_ptr, I mean, you can get raw-pointer from smart-pointer, but you simply can't get SAME-REF-COUNTER (wrapped in smart-pointer) from raw-pointer.

But you can:

  • Simply ensure your "void *" usage NEVER outlives the smart-pointer (else expect undefined-behaviour).
  • Or implement your own smart-pointer class, which has internal hash-map, that stores instances as value (with raw-pointer as key), and then later in a method finds and returns smart-pointer by "void *" input (like: static MySmartPtr fromRaw(void *)).
  • Or manually store std::shared_ptr in a hash-map (like above but without custom smart-pointer implemention).
Top-Master
  • 7,611
  • 5
  • 39
  • 71