10

I have class Data which can hold a pointer to an object. I want to be able to call its destructor manually later on, for which I need its address stored in a variable but it seems that taking the address of constructor/destructor is forbidden. Is there any way around this ?

struct Data {

  union {
    long i;
    float f;
    void* data_ptr;
  } _data;

  std::type_index _typeIndex;
  void (*_destructor_ptr)();

  template<typename T>
  void Init() {
  if constexpr (std::is_integral<T>::value) {
    //
  }
  else if constexpr (std::is_floating_point<T>::value) {
    //
  }
  else {
    _data.data_ptr = new T;
    _typeIndex = std::type_index(typeid(T));
    _destructor_ptr = &T::~T; // << -- can't do this
  }
}

3 Answers3

9

Store a lambda, suitably converted:

void (*_destructor_ptr)(void *v);

// ...

_destructor_ptr = [](void* v) { delete static_cast<T*>(v); };

Note that you must pass _data.data_ptr for v. If you intend to store a plain function pointer, the lambda may not capture or implicitly refer to _data.data_ptr.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
  • 1
    Why do you need the + sign before the lambda ? – const_iterator Oct 10 '18 at 11:16
  • 1
    @const_iterator for the plus sign see this one here https://stackoverflow.com/questions/17822131/resolving-ambiguous-overload-on-function-pointer-and-stdfunction-for-a-lambda – Pius Raeder Oct 10 '18 at 11:20
  • 2
    @const_iterator - [Sorcery](https://stackoverflow.com/q/18889028/817643) to force coercion into a pointer. It's not really needed here, the assignment itself would do it. But at times you want to ensure it happens, and it's a bit out of force of habit for me. – StoryTeller - Unslander Monica Oct 10 '18 at 11:20
  • This answer works, however, I ended up placing it in a helper struct instead of a lambda. This way, to my understanding, I'll have a function generated per type and not per object (see edited question) – const_iterator Oct 10 '18 at 12:07
  • 1
    @const_iterator - You get a unique lambda type for each type, not for each object. Converting it to a function pointer also produces one function per type. You can go the more verbose route, but it doesn't gain you anything here. If you like the more descriptive name of the custom struct, then by all means. – StoryTeller - Unslander Monica Oct 10 '18 at 12:11
  • 2
    @const_iterator - Also, if you produced an alternative answer to your question, it belongs in the answer section, not as an edit to your post. You can even accept it instead if you wish. It's how SO is meant to work. – StoryTeller - Unslander Monica Oct 10 '18 at 12:13
  • 1
    @StoryTeller Hmmm do you have something against signless (and negative) lambdas? They feel left out :( – Rakete1111 Oct 10 '18 at 17:26
  • 1
    @Rakete1111 - Just trying to keep a positive outlook on things ;) – StoryTeller - Unslander Monica Oct 10 '18 at 17:28
7

There's also this solution if your compiler doesn't support lambdas:

template<typename T>
struct DestructorHelper {
  static void Destroy(void * v) {
    delete static_cast<T*>(v);
  }
};

and use it as:

_destructor_ptr = &DestructorHelper<T>::Destroy;
2

I'll also add a solution for smart pointers:

template <class T>
struct DataPtrDeleter
{
    void operator()(void * p) { delete (T*)p; }
}

std::shared_ptr<void*> data_ptr(new T, DataPtrDeleter<T>());
//or
std::unique_ptr<void*, DataPtrDeleter<T>> data_ptr(new T, DataPtrDeleter<T>());
SirGuy
  • 10,660
  • 2
  • 36
  • 66