Можно ли использовать std :: array в качестве контейнера данных для структуры POD? - PullRequest
0 голосов
/ 10 января 2020

Я пытаюсь обновить какой-то устаревший код 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, я передаю тип структуры и тип значений в шаблоне; Я создал перегрузку для оператора [], разрешив доступ через индекс, скрывающий уродливое 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);
}

Теперь, когда я получил контекст из того, что могу наконец, go к реальному вопросу:

Можно ли использовать std :: array в качестве контейнера данных для структуры? означает инициализацию его через структуру; это позволило бы получить доступ к данным через переменную, используя саму структуру, и через индекс, используя 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);
}

Ответы [ 3 ]

0 голосов
/ 10 января 2020

Можно ли использовать массив std :: как контейнер данных для структуры?

A Структура POD в C ++ - это класс, который тривиально и имеет стандартную компоновку .
Начиная с std::array в качестве основного хранилища для "struct", вы фактически не определяете никакой struct с именованными элементами. Вам нужно написать класс, который содержит массив и члены, которые обращаются к индексам массива, и который может легко стать нетривиальным и не стандартным макетом .

Перегружая operator[], вы можете получить доступ к «массиву» членов структуры с массивоподобным синтаксисом, сохраняя определение struct и статус POD.

Если выглядит как 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";
}

Выход:

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
0 голосов
/ 10 января 2020

Если компиляция этой части кода как 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;

Так должен был быть написан исходный код C. Здесь вся информация о типах и особые случаи обрабатываются во время компиляции, а определение типов от int16_t до uint16_t четко определено, а их эффективные псевдонимы типов.

Вы можете сделать enum для особого случая var5 индексирование:

typedef enum
{
  var0_index = 0,
  myValue_index = 1,
  myParameter_index = 2,
  free_index = 3,
  var_1index = 4,
  var_2index = 5,
  var_3index = 6,
  var_4index = 7,
  var_5index = 7,
} var_t;

word_t word = 
{
  // different sorts of designated initializers can be mixed:
    .var0 = x,
  .myValue = y,      
  [var5_index] = z,
};

(хотя "слово" - это ужасное имя типа / переменной, так как в информатике термин слово относится к полному целочисленному типу. Поэтому, пожалуйста, придумайте что-нибудь получше.)

0 голосов
/ 10 января 2020

Вы не можете рассматривать отдельную переменную как массив.

Не идеально, но вы можете иметь методы доступа, например:

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());
    // ...
}
...