Потенциально возвращая различные типы объектов из одного метода

Я отвечаю за рефакторинг некоторого кода, который анализирует похожие (но разные файлы). Они отличаются тем, что имеют разное количество столбцов. Скажем, типы файлов называются MODEL_FILEа также COMPANY_FILE,
MODEL_FILE имеет следующий формат:

CAR_MODEL    CAR_COMPANY    MILEAGE

COMPANY_FILE имеет следующий формат:

CAR_COMPANY    MILEAGE

Результат разбора MODEL_FILE было бы std::map<Car_Model, std::map<Car_Company, double> > ; результат разбора COMPANY_FILE было бы std::map<Car_Company, double>,

Заголовочный файл выглядит примерно так:

typedef std::map<Car_Model, std::map<Car_Company, double> > Model_Data;
typedef std::map<Car_Company, double> Company_Data;

struct Data
{
Model_Data data_model;
Company_Data data_company;
};

bool parse_company_file(const std::string& path, Company_Data& data); // 1
bool parse_model_file(const std::string& path, Model_Data& data); // 2
bool parse_generic_file(bool is_company_file, const std::string& path, Data& data); // 3

Код синтаксического анализа действительно находится в 3, И то и другое 1 а также 2 внутренне вызывать 3, который знает (через логический параметр), имеет ли файл, который он собирается анализировать, 2 или 3 столбца. Только одно из полей в Data будет заполнен (который зависит от параметра bool). Затем функция, вызывающая 3 извлечет соответствующее поле структуры из заполненного Data структурировать и использовать, чтобы заполнить карту, которая была пройдена.

Таким образом, код для разбора файла только в одном месте (3). Снаружи, код в порядке (две разные точки входа, которые возвращают соответствующие данные), но внутренняя реализация мне не подходит (уловка использования структуры как способа использования единственного метода для потенциального заполнения два разных и независимых типа объектов).

Я думал об использовании наследования, так что универсальный метод получает указатель на общий базовый класс, который имеет два метода (add_model_data() а также add_company_data()). Это вызвало бы один или другой в зависимости от bool пары. Однако это более сложно и запутанно, и подразумевает нарушение абстракции, когда базовый класс должен знать о методах низших классов, он подвержен ошибкам и т. Д.

Вопрос в том, Можно ли каким-то образом сохранить логику синтаксического анализа в одном месте, но использовать другой (и, возможно, лучше) подход, чем struct для того, чтобы иметь дело с разными файлами?

2

Решение

std::variant а также boost::variant предназначены для типов «или» — тип, который является A или B. Так что это один из подходов.

Еще один более изощренный подход — помнить, что существует 3 числа — 0, 1 и бесконечность.

Этот подход сложнее, но реорганизует ваш код синтаксического анализа в очень общий. Я бы не стал беспокоиться об этом решении, поэтому я просто делаю набросок ниже, но оно позволит вам добавить версию этого формата в 4 или 20 столбцов с минимальными затратами труда после того, как вы ее напишите.

Анализатор столбцов принимает строку и возвращает значение типа T:

std::string -> T

или же

template<class T>
using column_parser = std::function<T(std::string)>;

(Мы можем сделать это более эффективным позже).

Учитывая N парсеров столбцов, мы можем построить map<T0, map<T1, map<T2, map<..., map<TN-2, TN-1>...>>>>,

template<class T0, class...Ts>
struct nested_map {
using type=T0;
};
template<class T0, class...Ts>
using nested_map_t = typename nested_map<T0, Ts...>::type;

template<class T0, class T1, class...Ts>
struct nested_map<T0, T1, Ts...> {
using type=std::map<T0, nested_map_t<T1, Ts...>>;
};

Это позволяет нам брать пачку типов и создавать карту.

template<class...Ts>
nested_map_t<Ts...> parse_file(std::string path, column_parser<Ts...> columns);

Это разбирает любое количество столбцов на вложенную карту.

Вы выставляете:

bool parse_company_file(const std::string& path, Company_Data& data) {
column_parser<Car_Company> company = // TODO
column_parser<double> milage = // TODO
try {
data = parse_file( path, company, milage );
} except (some_error) {
return false;
}
return true;
}
bool parse_model_file(const std::string& path, Model_Data& data) {
column_parser<Car_Model> model = // TODO
column_parser<Car_Company> company = // TODO
column_parser<double> milage = // TODO
try {
data = parse_file( path, model, company, milage );
} except (some_error) {
return false;
}
return true;
}

Теперь, чтобы написать parse_fileмы делаем что-то вроде (псевдокод)

template<class...Ts>
nested_map_t<Ts...> parse_file(std::string path, column_parser<Ts...> columns) {
nested_map_t<Ts...> retval;

auto f = open_file(path);

for( std::string line: get_lines(f)) {
std::vector<std::string> column_data = split_into_columns(line);
if (sizeof...(Ts) != column_data.size()) throw some_error;
index_upto<sizeof...(Ts)>()([&](auto...Is){
recursive_insert(retval, columns(column_data[Is])...);
});
}
return retval;
}

где index_upto либо это в C ++ 14 или заменен ручным расширением пакета и вспомогательной функцией, и recursive_insert(m, t0, ts...) берет «вложенную карту» M& m и куча элементов T const& и рекурсивно делает recursive_insert(m[t0], ts...) пока не будет 1 элемент, и это делает m = t0,

1

Другие решения

Других решений пока нет …

По вопросам рекламы ammmcru@yandex.ru
Adblock
detector