Групповые классы, основанные на шаблоне?

#c #design-patterns #polymorphism #variadic-templates

Вопрос:

Я не хочу стрелять себе в ногу, поэтому я предоставлю небольшой контекст на случай, если это проблема XY и есть лучший способ справиться с этим.

У меня много данных во множестве двоичных файлов; эти данные поступают с различных аппаратных средств, поэтому у меня есть данные в int , float , double , long double и т. Д.

Эти данные имеют заголовок для определения того, откуда они берутся (какое оборудование), тип данных ( int double , и т.д.), Что делалось при получении данных (физический анализ, калибровка, тестирование и т.д.) и другие вещи.

Лучший и наиболее интуитивно понятный способ обработки этих данных, о котором я думал, заключается в следующем:

Есть Header класс, который предоставляет информацию о данных:

 class Header {
    public:
        int event_id;
        int time_stamp;
        int data_size;
        int trigger_mask;
};
 

А затем создайте шаблон класса для данных (заголовок плюс вектор, содержащий фактические данные).:

 template<class T> class Data {
    public:
        Header data_header;
        std::vector<T> values;
};
 

Проблема в том, что люди могут захотеть, в зависимости от анализа, который они хотят провести, сгруппировать различные Data<X> объекты. Самым простым примером может быть, если люди захотят сгруппировать данные (одного и того же типа), собранные всеми EQUIPMENT X s.

 std::vector<Data<double>> x_signals;
 

Но что, если люди захотят сгруппировать все полученные данные EQUIPMENT X и просмотреть их? Это не обязательно все одного типа, одно и то же оборудование может вводить некоторые данные double для некоторых вещей и некоторые другие данные int .

Другим случаем было бы, когда люди читают Data объекты из двоичного файла. Они могут захотеть иметь a std::vector<Data> и вводить в него данные по мере чтения из файла. Но это также было бы невозможно.

Возможные решения, о которых я думаю, таковы:

  1. Ничего не делайте, и это будет проблемой пользователя в том, как обращаться с этими Data объектами. Они могли бы, например:
     std::vector<Data<int>> int_data;
    std::vector<Data<double>> double_data;
    std::vector<Data<float>> float data;
     

    И тогда они смогут запихнуть Data прочитанное в правильный контейнер. Если они хотят Data иметь определенную характеристику (идентификатор события, метку времени и т. Д.), Им придется выполнить цикл по всем контейнерам выше (и, возможно, сохранить индексы данных, которые они хотят, где-то в другом месте).

  2. Предоставьте вариадический шаблон, который сохраняет различные Data типы в кортеже:
     template<class ...Types> class DataPack {
        public:
            std::tuple<Types...> data_pack;
    };
     

    Но для этого потребуется, чтобы количество Data объектов было известно заранее для a DataPack . Мы не могли нажать Data на a DataPack , когда читали файл, и мы никак не можем узнать, сколько Data объектов мы собрали до того, как прочитали файл.

  3. Сделайте Data наследование от Header . Таким образом, мы можем иметь векторы «заголовков» и использовать полиморфизм. Проблема в том, что Data это не тип заголовка; Data имеет заголовок. Наследование кажется странным и неинтуитивным, что является чем-то вроде красного флага.

Я немного сбит с толку, и я не знаю, каков был бы наилучший подход. Я думаю обо всем этом неправильно? Все три варианта кажутся мне в разной степени уродливыми; я не чувствую себя счастливым или уверенным ни с одним из них.

Комментарии:

1. Возможно, использовать шаблон посетителя и а std::variant ?

2. @Натаноливер Я не думаю, что ОП нуждается в двойной отправке здесь. Похоже, простого std::variant будет достаточно.

3. @NathanOliver не могли бы вы немного расширить (возможно, в ответе) свое предложение? Я не знаком с std::variant этим , но если я правильно понял, ваше предложение состоит в том, чтобы изменить шаблон std::vector<T> в моем Data , например std::variant<std::vector<int>,std::vector<double>> ? Позволит ли это мне иметь, например, a std::vector<Data> ? Или каково предложение в отношении группировки данных?

4. @PhysicsPDF Я больше думал, что у тебя будет std::vector<std::variant<Data<T>, Data<U>, ..., Data<N>>>

5. @NathanOliver Итак, если я правильно понял; ваше предложение-вариант № 1 (ничего не делать и сохранить шаблон класса)? С помощью этого Data дизайна люди могут, если захотят, по-прежнему группировать их с помощью a std::vector std::variant , как вы предлагаете. Другие меньшие C fluent люди (такие как я) все равно будут делать то, что я предложил (разные векторы для каждого типа), но это их проблема, а не моя. Правильно ли это?

Ответ №1:

Не знаю, полностью ли я понимаю вашу проблему, но вот моя попытка. Есть класс, который содержит все: данные, машину, типы данных и т. Д. Добавьте функции доступа, чтобы люди могли извлекать данные и использовать их по мере необходимости.

 #include <vector>

enum class EquipmentType
{
    Unknown = 0,
    VoltMeter = 1,
    CurrentMeter = 2
};

enum class DataType
{
    Unknown = 0,
    INT = 1,
    DOUBLE = 2
};

class Data
{
public:
    Data();
    
    EquipmentType GetType() { return m_type; }
    
    int GetInt()
    {
        switch(m_dType)
        {
            case DataType::INT:     return m_nValue;
            case DataType::DOUBLE:  return static_cast<int>(m_dValue);
        }
    }
    
    double GetDouble()
    {
        switch(m_dType)
        {
            case DataType::INT:     return static_cast<double>(m_nValue);
            case DataType::DOUBLE:  return m_dValue;
        }
    }
private:
    int m_nValue;
    double m_dValue;
    std::vector<char> m_vValue;
    EquipmentType m_type;
    DataType m_dType;
};
 

Вышесказанное-действительно краткое представление о том, что нужно делать. В основном каждый класс может хранить данные любого типа. Например, когда кто-то звонит, GetInt() вы возвращаете правильное значение, основанное на типе данных, которые вы храните: иногда вам нужно будет привести данные. Я опустил vector решение, потому что мне не хочется все это печатать. Такой подход позволит вам хранить данные любого типа и возвращать их в зависимости от того, что запрашивает пользователь. Они также могут выполнять поиск в массиве классов и искать определенный тип оборудования, вызывая GetType() и проверяя, соответствует ли он тому, что они хотят для сравнения:

 std::vector<Data> dataObject;

for(autoamp; obj : dataObject)
{
    if(obj.GetType() == EquipmentType::VoltMeter)
    {
        // Do something here.
    }
}
 

Все это в общих чертах, но должно дать вам представление.

Комментарии:

1. Почему вы пытаетесь переосмыслить жизнь бедняги std::variant ?

2. @SergeyA: Потому что a std::variant -это союз, и он используется один раз. Если кто-то хочет изменить типы данных, возникает исключение. Этот объект данных удаляет это. Признаюсь, я не играл с std::variant этим, так что, возможно, мне не хватает некоторых тонкостей.

3. Действительно, так оно и есть. Значение варианта можно изменить на любой тип, поддерживаемый вариантом, в любое время, в противном случае это был бы довольно ограниченный тип.