C++ void* use for different data types

hi,

I have a class like (with much more other elements and virtual functions included):

1
2
3
class MyClass {
  void *m_pData;
}


And I want to know if it's "safe" to use "void *" type in order to allocate different data types which could have a different base size in bytes (char*, int, float, void*, Vector)...

Like doing:
1
2
3
int *pData = new int[2];
m_pData = (void *)pData;
// Copy data to my int array... 


Or:
1
2
3
Vector *pData = new Vector[4]; // 12 bytes class size (3 * 4 bytes), using 3 floats
m_pData = (void *)pData;
// Copy data to my vector array... 


Or should I better use "malloc" for this with a similar format:
1
2
3
Vector *pData = malloc(sizeof(Vector) * 4); 
m_pData = (void *)pData;
// Copy data to my int array... 


Then, will the use of "delete[]" operator on "m_pData" will be safe whatever the data type and its size I've previously allocated (I mean, does everything [whole data dynamically created] will be properly cleaned up?), or should I perform some checks and use "delete" (for example on one byte data type and one size) or another memory unallocation function in order to properly clean the data?

What is the best for this?

Thanks in advance.
Last edited on
Casting the pointer to void* and assigning it to m_pData is safe in itself, but question is how you are going to use it in a safe way afterwards.

Before you use delete[] you need to cast the pointer to its original type.
Last edited on
Thank you.
Well, so I can do something like (related to above code):

1
2
3
4
5
char *pszData = malloc(sizeof(char *) * (iLength + 1));
if(pszData) {
  memcpy(szData, szValue, iLength + 1);
  m_pData = (void *)pszData;
}


Then after:
1
2
3
4
char *pszData = (void *)m_pData;
if(pszData) {
  delete[] pszData;
}


Or (in case I have created an int):
1
2
3
4
int *pData = (int *)m_pData;
if(pData) {
  delete[] pData;
}


And everything is safe? Well, for sure it's just a matter or dealing with the right address.

But I was just wondering with the delete[] operator, if this will delete the whole data size in bytes without having the need of casting it back as you said. And also if I needed the use of the basic "delete" (without brackets) according to the size I had allocated.
And also maybe if using another operator/function than "delete" will be more appropriated for such need.
I have also a member that stores the data type, so I could do some "switch/checks" for such case, not a problem for me. But will a template with the type could work as alternative?

Or a more direct alternative, because doing something like:
1
2
3
4
5
6
7
8
9
if(m_iDataType == DT_Integer) {
  int *iData = (int *)m_pData;
  delete[] iData;
}
else if(m_iDataType == DT_Float) {
  float *flData = (float *)m_pData;
  delete[] flData;
}
etc.

Look like slighly annoying/trivial!
But if there is no other reliable method for my needs, I'll stick to it.

I have also a question, I have two modules, one which has an API of functions at through a "table" (a struct with the functions inside and their format, and a global variable matching to the function struct, so I call like "g_pFunctionsList->pfnMyFunction1(params...)").
And what is the performance difference between calling such function from an API available externally (other binary), and directly calling it from the current module (in case I implement it inside the current module).
I'm guessing it's much better when we call a function in its own binary (at least by logic due to how the programming things works), but since it's a matter of "calling an address" I'm wondering the level of performance difference for this case.
Last edited on
If you create something with malloc, then you should destroy it with free.
If you create a single object with new, then you should destroy it with delete.
If you create an array with new, then you should destroy it with delete[].

If you use malloc/free the type of the pointer doesn't matter because they work with void*. Note that malloc will not call constructors, free will not call destructors, and memcpy will not call copy assignment operators so they only work with trivial types.

If you use the wrong delete/delete[] or a pointer of the wrong type things might "work" but my understanding is that it's undefined behaviour even for trivial types such as int that doesn't have constructors and destructors so I wouldn't recommend it.
If you know what types you are working with you might be able to use std::variant.
1
2
3
4
5
std::variant<std::unique_ptr<int[]>, std::unique_ptr<double[]>> data;

data = std::make_unique<int[]>(2); // creates an int array of size 2 that will be  
                                   // automatically deallocated when the variant goes
                                   // out of scope or is assigned another value. 


Another way might be to use some kind of class hierarchy, possibly in combination with templates, but I guess it depends on how you actually going to use the data ...
Thanks you for those informations.

I do not wish to use std alternative (I even never used std stuff).


If you use the wrong delete/delete[] or a pointer of the wrong type things might "work" but my understanding is that it's undefined behaviour even for trivial types such as int that doesn't have constructors and destructors so I wouldn't recommend it.

So, you also told me in your first message that I had to cast to the original type before using delete[] operators, but is this needed for all the types that have the same size in bytes (like int, float, void*, etc.)?

I mean, does the cast to the original type is only needed due to this?

Because I could just do something like:
1
2
3
4
5
6
7
8
9
10
11
if(m_iDataType == DT_Vector) { // 3*4 bytes (float.x, float.y, float.z)
  Vector *vecData = (Vector *)m_pData;
  delete[] vecData;
}
else if(m_iDataType == DT_String) { // 1 byte
  char *pszData = (char *)m_pData;
  delete[] pszData;
}
else { // Int/Float/Void*, etc.
  delete[] m_pData;
}


Will it be good enough for my needs?


If you use malloc/free the type of the pointer doesn't matter because they work with void*. Note that malloc will not call constructors, free will not call destructors, and memcpy will not call copy assignment operators so they only work with trivial types.

So could I directly use "delete[] m_pData"? Or do I still need to cast to original type even with that "malloc" allocator?
Last edited on
So, you also told me in your first message that I had to cast to the original type before using delete[] operators, but is this needed for all the types that have the same size in bytes (like int, float, void*, etc.)?

Yes, that is my understanding. It might "happen to work" because new/delete is normally implemented using malloc/free but the C++ standard doesn't give such guarantees.

So could I directly use "delete[] m_pData"? Or do I still need to cast to original type even with that "malloc" allocator?

No, if you use malloc you should use free.
Last edited on
But if there is no other reliable method for my needs, I'll stick to it.

Have you considered that you might have a: http://xyproblem.info/
void* is unavoidable in some code, eg p-threads or other libraries that force it. But generally try to avoid it as much as possible.

the original class in the first post could completely avoid them by being a template class with a type* inside instead, as far as I can tell?
1
2
3
Vector *pData = malloc(sizeof(Vector) * 4); 
m_pData = (void *)pData;
// Copy data to my int array...  

This won't work if Vector (or any of its members or base classes) has a constructor. Malloc will not run the constructor. New will.

If you're writing C++, just stick with new and delete (and new[] and delete[]). That way you don't need to worry about whether constructors/destructors will be called. Just be sure that new and delete know the actual type of the data you plan to put in the memory so they know which constructor/destructor to call.
OK, thanks for those advices.


If you're writing C++, just stick with new and delete (and new[] and delete[]). That way you don't need to worry about whether constructors/destructors will be called. Just be sure that new and delete know the actual type of the data you plan to put in the memory so they know which constructor/destructor to call.

O.K., I get that, but that I would like to know, is if this matters for all the types or only the ones with a different base size in bytes. I didn't get an accurate answer regarding this.

In other terms, does this example:
1
2
3
4
5
6
7
8
9
10
11
if(m_iDataType == DT_Vector) { // 3*4 bytes (float.x, float.y, float.z)
  Vector *vecData = (Vector *)m_pData;
  delete[] vecData;
}
else if(m_iDataType == DT_String) { // 1 byte
  char *pszData = (char *)m_pData;
  delete[] pszData;
}
else { // Int/Float/Void*, etc.
  delete[] m_pData;
}


Is "safe" to be performed? Or do I need to check "m_iDataType" for all the types? I just need a "yes" or "no"!
Last edited on
Safe? I think so. But it's lazy.

If you create data with new Sometype[size] then you have to delete it with delete [] (pointer_to_Sometype) where pointer_to_Sometype has the same value as that returned by new Sometype[size]. What you have (using the same delete[] for types int, float, void* etc) might work, but why be lazy? Just write it out explicitly for all the types. At the very least, this might help if you have to debug the memory management.

Just write it out explicitly for all the types..

I would allocate everything as bytes and cast out of it. void* is asking for trouble, when you come back to it in 6 months to debug it, when someone else goes to read/modify it, etc.

unsigned char* ints = new unsigned char[1000*sizeof(int)];
int* ip = (int*)ints;
delete[] ints;




Initial post:
And I want to know if it's "safe" to use "void *" type in order to allocate different data types which could have a different base size in bytes (char*, int, float, void*, Vector)...

and:
What is the best for this?

Advice was given. The consensus was, you really shouldn't do it that way. It might or might not work, but casting void* like this is not robust and ABSOLUTELY don't mix malloc/free with new/delete.

Follow up post:
O.K., I get that, but that I would like to know, is if this matters for all the types or only the ones with a different base size in bytes. I didn't get an accurate answer regarding this.
and:
Is "safe" to be performed? Or do I need to check "m_iDataType" for all the types? I just need a "yes" or "no"!

The forum has told you that "yes" or "no" is not correct question. We are telling you that you probably want to rethink your design. Even if what you are trying to do is correct or safe (and I'm not sure that it is), extending or maintaining the design in the future will most likely be problematic. Listen to the collective wisdom of those posting here (remember, we are unbiased volunteers with no stake in your problem) that is suggesting that there is probably a better solution than the void* cast that you have jury-rigged.

Why don't you tell us what the problem is that you are trying to solve, not the solution that you have cooked up and are trying to fortify.

By the way--you are not alone in this boat. I am constantly going back to code and rewriting stuff that seemed simple and straight-forward when it was written, but "expedient" choices ("hacks") came back to bite us. I originally wrote some of the bad code. A lot of the bad code I inherited. The lesson learned is, if you are trying to force something to work in a way that requires non-standard usages (like void* casts), you should look from a different angle for a more robust solution rather than merely force through the tricky one that you first stumbled upon.

Edit:

Oh yeah, one more thing.

I do not wish to use std alternative (I even never used std stuff).


Learn to use the standard library. The standard library has LOTS of useful tools and data structures. It is part of the C++ language, so if you plan to be a C++ programmer, you should learn to use it. I would say that if you aren't familiar with at least the Standard Template Library (STL--part of the std:: namespace), you aren't really a C++ programmer.

You may not want to start with std::variant, but things like std::vector and std::map are really useful to know and understand.
Last edited on
Topic archived. No new replies allowed.