Click here to Skip to main content
15,896,557 members
Articles / Desktop Programming / Win32

The Power of void*

Rate me:
Please Sign up or sign in to vote.
2.83/5 (10 votes)
23 Dec 2009CPOL2 min read 29K   6   21
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)


Written By
United States United States
Shooting for the Moon.

Comments and Discussions

 
QuestionWhy oh why? Pin
jeffb425-Jan-10 7:03
jeffb425-Jan-10 7:03 
AnswerRe: Why oh why? Pin
Nisamudheen5-Jan-10 19:13
Nisamudheen5-Jan-10 19:13 
GeneralMy vote of 2 Pin
jeffb425-Jan-10 6:59
jeffb425-Jan-10 6:59 
GeneralMy vote of 1 Pin
Emilio Garavaglia28-Dec-09 7:53
Emilio Garavaglia28-Dec-09 7:53 
GeneralMy vote of 1 Pin
lusores24-Dec-09 10:07
lusores24-Dec-09 10:07 
GeneralRe: My vote of 1 Pin
Nisamudheen24-Dec-09 14:15
Nisamudheen24-Dec-09 14:15 
GeneralMy vote of 1 Pin
Jimmanuel24-Dec-09 7:26
Jimmanuel24-Dec-09 7:26 
GeneralRe: My vote of 1 Pin
Nisamudheen24-Dec-09 14:07
Nisamudheen24-Dec-09 14:07 
GeneralRe: My vote of 1 Pin
Jimmanuel24-Dec-09 15:27
Jimmanuel24-Dec-09 15:27 
It's a rare instance when you need to treat any data type as a single unit, and my point is that it's better to avoid it than to code around it.

Just because a feature exists doesn't mean that it's a good idea to go looking for places to use it. I think unions are sweet, but I only use them when absolutely necessary because they're not well known and easy can easily introduce bugs. Sure there are instances where you can't get around not knowing the type of something but it's not a situation anybody should try to get in to. Using a void * to store ints and doubles seems completely unnecessary. If a programmer knows that's what they're working with then it's simpler and clearer to use int and double pointers. Hiding them behind a void * is simply obfuscating the code and creating more chances for memory corruption.

What I don't like about the GENERIC_DATA struct is that it does absolutely nothing to ensure type safety. The data is deleted with a call to delete[], but what if the pointer doesn't point to an array? There's nothing there to ensure that it doesn't and you don't really give any good reason that it should. Simply because we need to "play with pointers" isn't a reason to use arrays. There's nothing wrong to having a pointer to a single object. What if i want it to point to a single int? There's nothing in the code stopping me from doing it and then it needs to free the memory with delete, not delete[]. Also, what if I don't set m_DataType correctly? If I either set it to the wrong type or don't set it at all then CleanMemory will corrupt memory or orphan it. The article even says that it's important in the comments but there's no defensive code to ensure it.

When using a void * any measure possible should be taken to ensure that the memory it references is used properly. I don't see anything that protects it from misuse in this example.

Badger | [badger,badger,badger,badger...]
GeneralRe: My vote of 1 Pin
Nisamudheen24-Dec-09 23:07
Nisamudheen24-Dec-09 23:07 
GeneralRe: My vote of 1 Pin
Jimmanuel25-Dec-09 6:44
Jimmanuel25-Dec-09 6:44 
GeneralRe: My vote of 1 Pin
Nisamudheen26-Dec-09 15:23
Nisamudheen26-Dec-09 15:23 
GeneralRe: My vote of 1 Pin
Jimmanuel27-Dec-09 2:52
Jimmanuel27-Dec-09 2:52 
GeneralRe: My vote of 1 Pin
Emilio Garavaglia28-Dec-09 7:51
Emilio Garavaglia28-Dec-09 7:51 
GeneralRe: My vote of 1 Pin
Jimmanuel28-Dec-09 8:39
Jimmanuel28-Dec-09 8:39 
GeneralWOW Pin
persian music24-Dec-09 1:24
persian music24-Dec-09 1:24 
GeneralMy vote of 1 Pin
Tim Craig23-Dec-09 18:23
Tim Craig23-Dec-09 18:23 
GeneralMy vote of 1 Pin
xliqz23-Dec-09 14:12
xliqz23-Dec-09 14:12 
GeneralRe: My vote of 1 [I'm a troll] Pin
ProtoBytes30-Dec-09 6:53
ProtoBytes30-Dec-09 6:53 
Questionobv.? Pin
xliqz23-Dec-09 12:08
xliqz23-Dec-09 12:08 
AnswerRe: obv.? Pin
Nisamudheen23-Dec-09 14:10
Nisamudheen23-Dec-09 14:10 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.