12

I have a generic class myClass that sometimes needs to store extra state information depending on the use. This is normally done with a void*, but I was wondering if I could use a std::unique_ptr<void, void(*)(void*)> so that the memory is released automatically when the class instance is destructed. The problem is that I then need a to use a custom deleter as deleting a void* results in undefined behaviour.

Is there a way to default construct a std::unique_ptr<void, void(*)(void*)>, so that I don't have construct it first with a dummy deleter then set a real deleter when I use the void* for a state struct? Or is there a better way to store state information in a class?

Here is some sample code:

void dummy_deleter(void*) { }

class myClass
{
public:
    myClass() : m_extraData(nullptr, &dummy_deleter) { }
    // Other functions and members
private:
    std::unique_ptr<void, void(*)(void*)> m_extraData;
};
steve9164
  • 426
  • 2
  • 11
  • 22
  • 7
    There really isn't much use for `void` pointers when we have templates. – Joseph Mansfield Mar 13 '13 at 12:04
  • 3
    This sounds like an [xy problem](http://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). – Pubby Mar 13 '13 at 12:06
  • Are you sure you want a `unique_ptr` here, and not a `shared_ptr`? – Alex Chamberlain Mar 13 '13 at 12:12
  • Why do you want to use a dummy deleter function to begin with? Can't you just pick a deleter function? – Nicol Bolas Mar 13 '13 at 12:17
  • I don't think you need to provide a deleter function when constructing `m_extraData`. If you leave it out then it will just be value-initialized (i.e. set to a null function pointer). This is harmless, since the deleter isn't called if the `unique_ptr` is destroyed when it's not holding anything. – Steve Jessop Mar 13 '13 at 12:21
  • @sftrabbit: so are you going to make `myClass` a template whose extra data type is a parameter, so that you can no longer have (for example) a container of instances with different extra data types? The `void*` here is providing (very basic) type erasure. – Steve Jessop Mar 13 '13 at 12:25
  • @Steve Jessop: not providing a function pointer on construction of a unique_ptr with a custom deleter is a compiler error ("error: static assertion failed: constructed with null function pointer deleter" in gcc 4.7.2). And replying to your second comment, that is exactly what I need (storage of many in containers) – steve9164 Mar 14 '13 at 09:25
  • @steve9164: ah, in that case I've misunderstood something about `unique_ptr`. I suppose I'm wrong about the deleter not being called if the object pointer is null -- otherwise gcc is being over-cautious on the basis you could later use `reset()` to assign a non-null object pointer without also assigning a new deleter. – Steve Jessop Mar 14 '13 at 09:37

1 Answers1

8

Probably a more intuitive way to store extra information would be to have an interface IAdditionalData with a virtual destructor. Whatever data structures you may have would inherit from IAdditionalData and be stored in a std::unique_ptr<IAdditionalData>.

This also provides a bit more type safety, as you would static cast between IAdditionalData and the actual type, instead of reinterpret_cast between void * and whatever data type.

Ed Rowlett-Barbu
  • 1,611
  • 10
  • 27
  • You can static_cast between void* and the target type too. – R. Martinho Fernandes Mar 13 '13 at 12:12
  • @R.MartinhoFernandes You can static_cast between void* and any type. It offers no type safety guarantee. As opposed to an interface. – Ed Rowlett-Barbu Mar 13 '13 at 12:13
  • static_cast from IAdditionalData to SomeDerivedAdditionalData isn't much safer than void* to SomeDerivedAdditionalData, though (at least not in any important way). – R. Martinho Fernandes Mar 13 '13 at 12:22
  • well, it is in the sense that you can't accidently static_cast from IAdditionalData to a type that doesn't derive from IAdditionalData without the compiler complaining. Of course, you can still cast from the pointer to DerivedTypeB instead of DerivedTypeA, but it is better. It also shows intent, with a descriptive name like IAdditionalData instead of simply void*. – Ed Rowlett-Barbu Mar 13 '13 at 12:26
  • The thing is, no matter how you cast, if you make that mistake, your code won't compile anyway (because your code will try to use the result, right?). It does not offer any significant advantage in that field. – R. Martinho Fernandes Mar 13 '13 at 12:34
  • I am not sure I follow. Could you provide an example of what you mean? – Ed Rowlett-Barbu Mar 13 '13 at 12:42
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/26096/discussion-between-zadirion-and-r-martinho-fernandes) – Ed Rowlett-Barbu Mar 13 '13 at 12:42
  • This is a good solution, however I read [here](http://stackoverflow.com/questions/8274451/well-how-does-the-custom-deleter-of-stdunique-ptr-work) that this results in undefined behaviour when delete is called (using a unique_ptr with base class). Is this only if the destructor of Base is not virtual? – steve9164 Mar 14 '13 at 09:38
  • Or is this if you have a deleter which takes a void*? (in which case the answer from @Zadirion would be correct because the default deleter takes T*, not void*) – steve9164 Mar 14 '13 at 09:46
  • 2
    @steve9164: there are two relevant things that can cause UB: a `unique_ptr` where `Base` doesn't have a virtual destructor, and a `unique_ptr` where the deleter converts to `Derived*`. The latter is UB because the sequence of conversions `Derived* -> Base* -> void* -> Derived*` doesn't necessarily result in a valid `Derived*` pointer. I think it can be saved by having the deleter convert `void*` -> `Base*` -> `Derived*`, though. – Steve Jessop Mar 14 '13 at 09:50
  • @SteveJessop nicely explained! – Ed Rowlett-Barbu Mar 14 '13 at 11:50
  • This will not work in embedded bare metal system where you can not use the default deleter. – user1911091 Jul 06 '22 at 15:47