void* 的力量






2.83/5 (10投票s)
简要解释数据类型 void* 的一种强大用法。
引言
在任何我们尝试使用任何编程语言解决的业务问题中,我们都需要保存和操作各种数据类型,例如 int、float、double、char 等。大多数情况下,我们还需要操作许多用户定义的数据类型。如何在内存中组织不同的数据类型是一个设计选择。如果定义一种可以保存任何数据类型的数据类型,那么将会产生干净且可扩展的代码。
除了将用于临时目的的自动变量外,我们实际操作的真实数据始终保存在堆中分配的内存中。使用堆内存是因为我们不知道运行时将作为输入的数据的大小。本文解释了如何将堆中分配的不同数据类型作为单个数据类型保存和操作。
这里遵循的一种策略是将任何类型的数据都保存为该类型的一个数组。这是因为,为了将任何数据类型作为 void* 保存,我们需要使用指针。优点是,基本上,任何指针类型都可以保存在 void 指针中。如我们所知,基本上有两种类型的数据:数值和字符串。在这里,可以使用单个指针处理任何数值数据。对于字符串,我们需要一个双指针。由于会有多个字符串,并且每个字符串本身就是一个数组,因此我们需要一个双指针。
以下部分简要解释使用 void 指针实现通用数据类型的每个步骤。
定义一个枚举来表示数据类型
作为第一步,我们需要定义一个枚举,该枚举可以有意义地定义我们将要处理的每种数据类型。
enum GENERIC_DATA_TYPE
{
   GENERIC_TYPE_NONE = 0,
   GENERIC_TYPE_INT,
   GENERIC_TYPE_SHORT,
   GENERIC_TYPE_DOUBLE,
   GENERIC_TYPE_CHAR,
};
我们还可以为任何用户定义的数据类型定义枚举。
定义通用数据类型
作为第二步,我们需要定义一种可以保存任何类型数据的数据类型。
struct GENERIC_DATA
{
   GENERIC_DATA_TYPE m_DataType;
   UINT              m_DataSize;
   void*             m_ptrData;
};
结构的每个成员都自明自义。
Using the Code
以下是一个使用上述定义的 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:
               {
                   // 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;
}
上述代码只是演示了 GENERIC_DATA 的用法。我们可以定义任何复杂的用户定义数据类型,并将这些对象作为通用数据保存。如果您有任何 Windows HANDLE 的任何设备或同步对象,可以在上述枚举中进行条目并相应地操作它。这种方法对于任何项目的集中数据管理都非常有用。
关注点
关键点是,我们可以基本上将任何指针类型分配给 void 指针。在访问数据时,检查数据类型并使用 reinterpret_cast() 运算符将其转换为真实的数据类型。
历史
- 2009 年 12 月 23 日 - 文章发布。

