Introduction
In every business problem that we try to solve using any programming language, we will have to hold and manipulate various data types such as int, float, double, char, etc. Most of the time, we also have to manipulate a number of user-defined data types. How we group different data types in memory is a design choice. If we define a data type that can hold any data type, then it would result in a clean and extensible code.
Apart from the automatic variables that will be used for temporary purposes, the real data that we manipulate will always be kept in memory that is allocated in heap. The use of heap memory is because we don't know the size of data that will be coming as input, at runtime. This article explains how to hold and manipulate different data types that are allocated in heap, as a single data type.
One strategy that is followed here is that any type of data is kept as an array of that type. This is because, in order to hold any data type as a void*
, we need to play with pointers. The advantage is that, basically, any pointer type can be kept in a void pointer. As we know, there are basically two types of data: numeric and string. Here, any numeric data can be handled using a single pointer. For strings, we need a double pointer. Since there will be multiple strings and since each string itself is an array, we need a double pointer.
The following sections briefly explain each step of implementing a generic data type using a void pointer.
Define an enum for Data Types
As the first step, we need to define an enum that can meaningfully define each data type that we are going to handle.
enum GENERIC_DATA_TYPE
{
GENERIC_TYPE_NONE = 0,
GENERIC_TYPE_INT,
GENERIC_TYPE_SHORT,
GENERIC_TYPE_DOUBLE,
GENERIC_TYPE_CHAR,
};
We can also define enums for any user-defined data type.
Defining a Generic Data Type
As the second step, we need to define a data type that can hold any type of data.
struct GENERIC_DATA
{
GENERIC_DATA_TYPE m_DataType;
UINT m_DataSize;
void* m_ptrData;
};
All the members of the structure are self explanatory.
Using the Code
The following is a sample program that makes use of the above defined GENERIC_DATA
:
#include "windows.h"
#include "vector"
using namespace std;
enum GENERIC_DATA_TYPE
{
GENERIC_TYPE_NONE = 0,
GENERIC_TYPE_INT,
GENERIC_TYPE_SHORT,
GENERIC_TYPE_DOUBLE,
GENERIC_TYPE_CHAR,
};
#define SAFE_ARRAY_DELETE( ptrArray ) \
delete[] ptrArray;\
ptrArray = 0;
struct GENERIC_DATA
{
GENERIC_DATA_TYPE m_DataType;
UINT m_DataSize;
void* m_ptrData;
GENERIC_DATA() : m_DataType( GENERIC_TYPE_NONE ),
m_DataSize( 0 ),
m_ptrData( 0 )
{
}
~GENERIC_DATA()
{
CleanMemory();
}
bool CleanMemory()
{
try
{
switch( m_DataType )
{
case GENERIC_TYPE_INT:
{
int* ptrInt = reinterpret_cast<int*>( m_ptrData );
SAFE_ARRAY_DELETE( ptrInt );
break;
}
case GENERIC_TYPE_SHORT:
{
short* ptrShort = reinterpret_cast<short*>( m_ptrData );
SAFE_ARRAY_DELETE( ptrShort );
break;
}
case GENERIC_TYPE_DOUBLE:
{
double* ptrDouble = reinterpret_cast<double*>( m_ptrData );
SAFE_ARRAY_DELETE( ptrDouble );
break;
}
case GENERIC_TYPE_CHAR:
{
char** ptrString = reinterpret_cast<char**>( m_ptrData );
for( UINT uCounter = 0; m_DataSize > uCounter; ++uCounter )
{
SAFE_ARRAY_DELETE( ptrString[uCounter]);
}
SAFE_ARRAY_DELETE( ptrString );
break;
}
}
m_DataSize = 0;
m_DataType = GENERIC_TYPE_NONE;
return true;
}
catch( ... )
{
}
return false;
}
};
typedef vector<GENERIC_DATA*> GENERIC_DATA_VECTOR;
int main()
{
GENERIC_DATA_VECTOR vData;
GENERIC_DATA* ptrData = new GENERIC_DATA();
const int INT_COUNT = 10;
int* ptrIntArray = new int[INT_COUNT];
int nCounter = 0;
for( nCounter = 0; INT_COUNT > nCounter; ++nCounter )
{
ptrIntArray[nCounter] = rand();
}
ptrData->m_DataType = GENERIC_TYPE_INT;
ptrData->m_DataSize = INT_COUNT;
ptrData->m_ptrData = ptrIntArray;
vData.push_back( ptrData );
const int STRING_COUNT = 5;
char** ptrStringArray = new char*[STRING_COUNT];
const char* STRING1 = "STRING1";
int nStringLength = 0;
for( nCounter = 0; STRING_COUNT > nCounter; ++nCounter )
{
nStringLength = lstrlen( STRING1 );
ptrStringArray[nCounter] = new char[nStringLength + 1];
lstrcpy( ptrStringArray[nCounter], STRING1 );
}
ptrData = new GENERIC_DATA();
ptrData->m_DataType = GENERIC_TYPE_CHAR;
ptrData->m_DataSize = STRING_COUNT;
ptrData->m_ptrData = ptrStringArray;
vData.push_back( ptrData );
GENERIC_DATA_VECTOR::iterator DATA_VECTOR_END = vData.end();
GENERIC_DATA_VECTOR::iterator itrData = vData.begin();
GENERIC_DATA* ptrDataToProcess = 0;
for( ; itrData != DATA_VECTOR_END; ++itrData )
{
ptrDataToProcess = ( *itrData );
if( GENERIC_TYPE_CHAR == ptrDataToProcess->m_DataType )
{
char** ptrStringArray =
reinterpret_cast<char**>( ptrDataToProcess->m_ptrData );
for( nCounter = 0; ptrDataToProcess->m_DataSize > nCounter; ++nCounter )
{
printf( "\n %s", ptrStringArray[nCounter]);
}
}
if( GENERIC_TYPE_INT == ptrDataToProcess->m_DataType )
{
int* ptrIntArray = reinterpret_cast<int*>( ptrDataToProcess->m_ptrData );
for( nCounter = 0; ptrDataToProcess->m_DataSize > nCounter; ++nCounter )
{
printf( "\n %d", ptrIntArray[nCounter]);
}
}
}
DATA_VECTOR_END = vData.end();
itrData = vData.begin();
for( ; itrData != DATA_VECTOR_END; ++itrData )
{
delete ( *itrData );
}
return 0;
}
The above code simply demonstrates the usage of GENERIC_DATA
. We can define any complex user defined data type and hold such objects as generic data. If you have any Windows HANDLE
of any device or synchronization object, you can make an entry in the enum defined above and manipulate it accordingly. This kind of approach will be very useful for centralized management of data in any project.
Points of Interest
The key point is that, we can basically assign any pointer type to a void pointer. When accessing data, check the data type and use the reinterpret_cast()
operator for converting it to the real data type.
History
- December 23, 2009 - Article posted.