Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / Win32

The Power of void*

2.83/5 (10 votes)
23 Dec 2009CPOL2 min read 30K  
Briefly explains one of the powerful usages of the data type void*.

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.

C++
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.

C++
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:

C++
#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:
               {
                   // Since string is kept as an array of string,
                   // we need to iterate each string
                   // and delete.
                   char** ptrString = reinterpret_cast<char**>( m_ptrData );
                   for( UINT uCounter = 0; m_DataSize > uCounter; ++uCounter )
                   {
                        SAFE_ARRAY_DELETE( ptrString[uCounter]);
                   }

                   // Now delete the double pointer.
                   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;
    
    // Allocate memory to hold the data
    GENERIC_DATA* ptrData = new GENERIC_DATA();

    // PUT SOME INTERGERS
    // Of course the array size would be determined at runtime.
    const int INT_COUNT = 10;
    int* ptrIntArray = new int[INT_COUNT]; 

    int nCounter = 0;

    // Fill ptrIntArray with some integers
    for( nCounter = 0; INT_COUNT > nCounter; ++nCounter )
    {
        ptrIntArray[nCounter] = rand();
    }
    // It is verly important to set the correct type here.
    ptrData->m_DataType = GENERIC_TYPE_INT;
    ptrData->m_DataSize = INT_COUNT;
    ptrData->m_ptrData  = ptrIntArray;

    // Now put the data in the vector;
    vData.push_back( ptrData );
    

    // PUT SOME STRINGS
    const int STRING_COUNT = 5;
    char** ptrStringArray = new char*[STRING_COUNT];

    // Fill string array with some string
    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();
    // It is verly important to set the correct type here.
    ptrData->m_DataType = GENERIC_TYPE_CHAR;
    ptrData->m_DataSize = STRING_COUNT;
    ptrData->m_ptrData  = ptrStringArray;

    // Now put the data in the vector;
    vData.push_back( ptrData );



    // Now, at a later time we can manipulate each
    // or the required type in the vector as below.
    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 );

        // Look for string
        if( GENERIC_TYPE_CHAR == ptrDataToProcess->m_DataType )
        {
            char** ptrStringArray = 
               reinterpret_cast<char**>( ptrDataToProcess->m_ptrData );

            // Process the string
            for( nCounter = 0; ptrDataToProcess->m_DataSize > nCounter; ++nCounter )
            {
                printf( "\n %s", ptrStringArray[nCounter]);
            }
        }

        // Look for integer
        if( GENERIC_TYPE_INT == ptrDataToProcess->m_DataType )
        {
            int* ptrIntArray = reinterpret_cast<int*>( ptrDataToProcess->m_ptrData );

            // Process integers
            for( nCounter = 0; ptrDataToProcess->m_DataSize > nCounter; ++nCounter )
            {
                printf( "\n %d", ptrIntArray[nCounter]);
            }
        }

    }

    // Once we finish with the data, iterate the vector and delete each entry.
    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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)