【问题标题】:Potentially returning different types of objects from a single method可能从单个方法返回不同类型的对象
【发布时间】:2017-06-16 22:14:14
【问题描述】:

我负责重构一些解析相似(但不同文件)的代码。它们的不同之处在于它们具有不同数量的列。假设文件类型称为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

解析代码确实在312 都在内部调用 3,它知道(通过布尔参数)它要解析的文件是 2 列还是 3 列。 Data 中只有一个字段将被填充(取决于 bool 参数)。然后,调用3 的函数将从填充的Data 结构中检索结构的适当字段,并用于填充它已传递的映射。

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

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

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

【问题讨论】:

  • std::map&lt;Car_Model, Car_Company, double&gt; 没有意义。
  • 当您说 std::map&lt;Car_Model, Car_Company, double&gt; 时,您实际上是指 std::map&lt;Car_Model,std::pair&lt;Car_Company, double&gt;&gt; 吗?第三个参数是比较器,所以实际上它不应该以double作为第三个参数进行编译
  • 我的错,这是一张地图。我已经编辑了问题。
  • 您是否尝试使 parse_generic_file 函数实际上是通用的(即模板函数)?似乎可以解决问题。同样在您当前的实现中,返回值应该是联合而不是结构。这将使代码更具表现力。

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


【解决方案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&lt;T0, map&lt;T1, map&lt;T2, map&lt;..., map&lt;TN-2, TN-1&gt;...&gt;&gt;&gt;&gt;

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&amp; m 和一堆元素T const&amp; 并递归执行recursive_insert(m[t0], ts...)直到有 1 个元素并且它确实是 m = t0

【讨论】:

  • 很棒的回复,感谢您的辛勤工作。 boost::variant 成功了 :)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-02-25
  • 1970-01-01
  • 2016-05-14
  • 2012-05-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多