可以从单个方法中

时间:2017-06-16 10:57:18

标签: c++ c++11 return-type

我负责重构一些解析类似(但不同的文件)的代码。它们的不同之处在于它们具有不同数量的列。我们假设文件类型称为MODEL_FILECOMPANY_FILEMODEL_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中。 12都在内部调用3,它知道(通过布尔参数)它要解析的文件是2列还是3列。 Data中只有一个字段会被填充(哪个字段取决于bool参数)。然后,调用3的函数将从填充的Data结构中检索结构的相应字段,并用于填充已传递的映射。

通过这种方式,只在一个地方(3)解析文件的代码。从外部来看,代码很好(两个不同的入口点返回适当的数据),但内部实现并不适合我(使用结构作为使用单个方法的方法)可能填充两种不同且独立的对象类型。)

我想过使用继承,因此泛型方法接收一个指向公共基类的指针,该基类有两个方法(add_model_data()add_company_data())。它会根据bool参数调用其中一个或另一个。然而,这更复杂和令人困惑,并且意味着通过让基类知道低级类的方法,它容易出错等来打破抽象。

问题是,是否有可能以某种方式将解析逻辑保留在一个地方,但使用与struct不同(并且可以说更好)的方法来处理不同的文件?

1 个答案:

答案 0 :(得分:1)

std::variantboost::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_uptothis in C++14或由手动包扩展和辅助函数替换,而recursive_insert(m, t0, ts...)采用“嵌套映射”M& m和一堆元素{ {1}}并递归执行T const&,直到有1个元素且recursive_insert(m[t0], ts...)为止。