是否可以将std :: array用作POD结构的数据容器?

问题描述 投票:0回答:3

我正在尝试更新一些旧的C代码,这些代码将数组用作数据容器,并通过宏进行命名访问,从而可以使用更优雅的C ++ 17解决方案(如果可用,将更新为C ++ 20,可能的C ++欢迎提供20种解决方案)。抱歉,如果有很多代码,但这是我关于布局的第一个StackOverflow问题建议,欢迎您。

当前的旧版C设计:

#define WORD_ARR_SIZE   100
int16_t         word_arr[WORD_ARR_SIZE];    //two byte variables

#define var0        word_arr[0] //macros are used to hide the array and use its members like variables
#define myValue     word_arr[1]
#define myParameter word_arr[2]
#define free        ((uint16_t)word_arr[3]) //'hidden' explicit cast needed when using the array as all data must be of the same type
#define var1        word_arr[4]
#define var2        word_arr[5]
#define var3        word_arr[6]
#define var4        word_arr[7]
#define var5        word_arr[7] //very easy to write the wrong index when adding new 'variables'

extern int send(int16_t* arr, size_t size); //The array is necessary as it needs to be fed to a library (code cannot be modified)

int main()
{
    (uint16_t)var1 = UINT16_MAX; //'visible' explicit cast needed when using the array as all data is of the same type
    myValues = 50;

    for(int a = 20; a < 30; a++)
    {
        word_arr[a] = 10;   //array is also used like it should be
    }

    return send(word_arr, WORD_ARR_SIZE);
}

我解决这个问题的第一个尝试是使用结构而不是数组,这消除了显式的强制转换和对宏的需要,但是缺点是缺少了数组实现所具有的通过索引的简单访问,而是将其替换为难看的reinterpret_cast。

//no need for pragma pack, the code doesn't care about padding
struct word_arr_t
{
    int16_t var0;   //no more macros
    int16_t myValue;    
    int16_t myParameter;
    uint16_t free;   //no need for cast, value is alredy declared using the correct type
    int16_t var1;       
    int16_t var2;  //no way to get the index as if it was an array by simply using the value.
    int16_t var3;       
    int16_t var4;       
    int16_t var5;       
}word_arr;

constexpr size_t WORD_ARR_SIZE = sizeof(word_arr_t) / sizeof(uint16_t);

auto word_arr_p = reinterpret_cast<int16_t*>(&word_arr); //needed for indexed access

extern int send(int16_t* arr, size_t size);

int main()
{
    word_arr.var1 = UINT16_MAX;
    word_arr.myValues = 50;

    for(int a = 20; a < 30; a++)
    {
        word_arr_p[a] = 10;   //'hidden' pointer arithmetic to access the struct like an array
    }

    return send(word_arr_p, sizeof(word_arr_t));
}

当前解决方案:我创建了一个名为SmartStruct的自定义模板化类,在模板中传递了struct类型和values类型。我为operator []创建了一个重载,允许通过索引进行访问,从而隐藏丑陋的reinterpret_cast;

/**
 * \brief   A wrapper for structs made of object of the same type, allows indexed access
 * \tparam StructT  struct type
 * \tparam DataT    struct data type
 */
template <typename StructT, typename DataT>
class SmartStruct
{
    DataT* m_dataPointer;
public:
    /**
     * \brief let the struct be accessible from the outside as well
     */
    StructT Data;
    const size_t Count;

    /**
     * \brief Default constructor
     */
    SmartStruct();

    /**
     * \brief Construct by struct copy
     * \param data struct to copy
     */
    explicit SmartStruct(const StructT& data);

    /**
     * \brief operator to access struct in array style 
     * \param index element to access
     * \return element, if index >= size then first element
     */
    DataT& operator[](size_t index);
};

template <typename StructT, typename DataT>
SmartStruct<StructT, DataT>::SmartStruct() : Data{}, Count{ sizeof Data / sizeof(DataT) }
{
    m_dataPointer = reinterpret_cast<DataT*>(&Data);
}

template <typename StructT, typename DataT>
SmartStruct<StructT, DataT>::SmartStruct(const StructT& data) : Count{ sizeof data / sizeof(DataT) }
{
    //copy the struct
    Data = data;
    m_dataPointer = reinterpret_cast<DataT*>(&Data);
}

template <typename StructT, typename DataT>
DataT& SmartStruct<StructT, DataT>::operator[](size_t index)
{
    if (index >= Count)
    {
        return *m_dataPointer;
    }

    return m_dataPointer[index];
}

用法示例:

struct word_arr_t
{
    int16_t var0;
    int16_t myValue;
    int16_t myParameter;
    uint16_t free;
    int16_t var1;
    int16_t var2;
    int16_t var3; //Still no way to get array index from variable name
    int16_t var4;
    int16_t var5;
};

SmartStruct<word_arr_t, word> smart_word_arr{}; //Would love it if I could use std containers interface without having to implement it all by hand...


extern int send(int16_t* arr, size_t size);

int main()
{
    word_arr_t& word_arr = smart_word_arr.Data;

    word_arr.var1 = UINT16_MAX;
    word_arr.myValues = 50;

    for(int a = 20; a < 30; a++)
    {
        smart_word_arr[a] = 10;
    }

    return send(&smart_word_arr[0], smart_word_arr.Count);
}

现在,我摆脱了背景,终于可以进入真正的问题:

是否可以将std :: array用作结构的数据容器?意思是通过struct初始化它;这样就可以使用结构本身通过变量访问数据,而使用std :: array通过索引访问数据,并带有std接口的额外好处,而不必重新实现它。

我目前为使该解决方案起作用而进行的尝试:

struct word_arr_t
{
    int16_t var0;
    int16_t myValue;
    int16_t myParameter;
    uint16_t free;
    int16_t var1;
    int16_t var2;
    int16_t var3; //Still no way to get array index from variable name
    int16_t var4;
    int16_t var5;
}word_struct;

std:.array<int16_t, sizeof(word_arr_t) / sizeof(word)> word_array{};
//std:.array<int16_t, sizeof(word_arr_t) / sizeof(word)> word_array{&word_struct}; would be lovely if I could do this.
//word_array.Data = reinterpret_cast<int16_t*>(&word_struct); this would also be good.

extern int send(int16_t* arr, size_t size);

int main()
{
    word_struct.var1 = UINT16_MAX;
    word_struct.myValues = 50;

    //copy struct into array, very very bad as it's not usable unless you know when
    //code writes to the struct and when code writes to the array,
    //this could be solved by wrapping the array into a read only object but still not ideal
    //and extremely slow especially if the struct is very large
    memcpy(word_array.Data, &word_struct, sizeof(word_struct));

    for(auto& a : word_array)
    {
        a = 10;
    }

    return send(word_array.Data, word_array.Size);
}
c++ c arrays struct embedded
3个回答
0
投票

您不能将单独的变量视为数组。

不太理想,但是您可以使用访问器,例如:

struct word_arr_t
{
    std::array<std::int16_t, 100> data{}; // your array

    int16_t& var0() { return data[0]; }
    int16_t& myValue()  { return data[1]; }    
    int16_t& myParameter { return data[2]; }
    uint16_t free() const { return static_cast<uint16_t>(data[3]); }
    void set_free(uint16_t value) { data[3] = static_cast<int16_t>(value); }
    int16_t& var1() { return data[4]; }
    int16_t& var2() { return data[5]; }
    int16_t& var3() { return data[6]; }
    int16_t& var4() { return data[7]; }
    int16_t& var5() { return data[8]; }
};

int main()
{
    word_arr_t word_arr;
    word_arr.var1() = INT16_MAX;
    word_arr.myValues() = 50;

    send(word_arr.data.data(), word_arr.data.size());
    // ...
}

或枚举索引:

enum Index
{
    var0,
    myValue,
    myParameter,
    free,
    var1,
    var2,
    var3,
    var4,
    var5,
};

struct word_arr_t
{
    std::array<std::int16_t, 100> data{}; // your array
};


int main()
{
    word_arr_t word_arr;
    word_arr.data[Index::var1] = INT16_MAX;
    word_arr.data[Index::myValues] = 50;

    send(word_arr.data.data(), word_arr.data.size());
    // ...
}

0
投票

是否有可能将std :: array用作结构的数据容器?

C ++中的[A C0]是POD struct并具有trivial的类。从standard layout开始作为“结构”的基础存储意味着您实际上并没有使用命名成员定义任何std::array。您将必须编写一个包含该数组和访问该数组的索引的成员的类,这些类可以很容易地变为非平凡非标准布局

通过重载struct,您可以使用类似数组的语法访问结构的“数组”成员,同时保留operator[]定义和POD状态。

如果它像struct一样[[looks,并且像struct一样是[],那么就最终用户而言,它可能足够好

这里是一个示例程序,演示了:

std::array

输出:

#include <iostream> #include <stdexcept> #include <type_traits> struct ArrPODStruct { int someHeaderValue1; int someHeaderValue2; int someHeaderValue3; int a0; int a1; int a2; int a3; int a4; int operator[] (int i) { switch (i) { case 0: return a0; case 1: return a1; case 2: return a2; case 3: return a3; case 4: return a4; default: throw std::out_of_range("..."); } } }; int main() { ArrPODStruct arr; // Put some useful data in arr... std::cout << "Is POD: " << (std::is_pod<ArrPODStruct>() ? "Yes" : "No") << "\n"; std::cout << "Element 0: " << arr.a0 << " or " << arr[0] << "\n"; std::cout << "Element 1: " << arr.a1 << " or " << arr[1] << "\n"; std::cout << "Element 2: " << arr.a2 << " or " << arr[2] << "\n"; std::cout << "Element 3: " << arr.a3 << " or " << arr[3] << "\n"; std::cout << "Element 4: " << arr.a4 << " or " << arr[4] << "\n"; }

0
投票
如果将这部分代码编译为C是一种选择,那么到目前为止,最优雅的解决方案是使用Is POD: Yes Element 0: 0 or 0 Element 1: 0 or 0 Element 2: 0 or 0 Element 3: 0 or 0 Element 4: 0 or 0 。由于对齐在这里不是问题,因此不会有填充。这只能在C中完成,这允许通过联合进行类型修剪。

union

这是应该编写原始C代码的方式。在这里,所有类型信息和特殊情况都在编译时进行处理,并且从typedef union
{
  struct // C11 anonymous struct
  {
    int16_t  var0;
    int16_t  myValue;
    int16_t  myParameter,
    uint16_t free;
    int16_t  var1;
    int16_t  var2;
    int16_t  var3;
    int16_t  var4;
    // var5 not declared on purpose
  };

  int16_t arr [WORD_ARR_SIZE];

} word_t;
int16_t的类型调整是明确定义的,并且它们的有效类型别名也是如此。 

您可以为特殊情况下的uint16_t索引创建一个enum

var5

(“ word”是一个可怕的类型/变量名称,因为在计算机科学中术语word是指完整的整数类型。因此,请提出更好的方法。)
© www.soinside.com 2019 - 2024. All rights reserved.