#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>
и вводить в него данные по мере чтения из файла. Но это также было бы невозможно.
Возможные решения, о которых я думаю, таковы:
- Ничего не делайте, и это будет проблемой пользователя в том, как обращаться с этими
Data
объектами. Они могли бы, например:std::vector<Data<int>> int_data; std::vector<Data<double>> double_data; std::vector<Data<float>> float data;
И тогда они смогут запихнуть
Data
прочитанное в правильный контейнер. Если они хотятData
иметь определенную характеристику (идентификатор события, метку времени и т. Д.), Им придется выполнить цикл по всем контейнерам выше (и, возможно, сохранить индексы данных, которые они хотят, где-то в другом месте). - Предоставьте вариадический шаблон, который сохраняет различные
Data
типы в кортеже:template<class ...Types> class DataPack { public: std::tuple<Types...> data_pack; };
Но для этого потребуется, чтобы количество
Data
объектов было известно заранее для aDataPack
. Мы не могли нажатьData
на aDataPack
, когда читали файл, и мы никак не можем узнать, сколькоData
объектов мы собрали до того, как прочитали файл. - Сделайте
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>>
? Позволит ли это мне иметь, например, astd::vector<Data>
? Или каково предложение в отношении группировки данных?4. @PhysicsPDF Я больше думал, что у тебя будет
std::vector<std::variant<Data<T>, Data<U>, ..., Data<N>>>
5. @NathanOliver Итак, если я правильно понял; ваше предложение-вариант № 1 (ничего не делать и сохранить шаблон класса)? С помощью этого
Data
дизайна люди могут, если захотят, по-прежнему группировать их с помощью astd::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. Действительно, так оно и есть. Значение варианта можно изменить на любой тип, поддерживаемый вариантом, в любое время, в противном случае это был бы довольно ограниченный тип.