在代码中的同一位置最有效地填充静态标题列表和动态值列表

filling static title list and dynamic value lists at the same place in code and most efficiently

本文关键字:列表 标题 填充 静态 动态 有效地 代码 位置      更新时间:2023-10-16

我有一些代码可以递归地从分层数据结构中提取值,例如:

struct A{
  int i;
  int t;
};

struct B{
  float64 fl;
  A       a;
  QString str;
};

struct C{
  bool cb;
  A a;
  B b;
};

目前,结构本身实现了一个fillData函数,该函数用那里的数据和子数据填充一些键值映射。例如:

void C::fillData(DataMap* io_map){
  (*io_map)["cb"] = cb;
  DataMap* am = new DataMap();
  DataMap* am = new DataMap();
  a.fillData(am); 
  b.fillData(bm); 
  (*io_map)["a"] = am;
  (*io_map)["b"] = bm;
}

数据结构要复杂得多,并且不会输出所有值。进一步的步骤是通过一些通用的datamap2csv组件将提取的数据写入某些csv。

问题是这最近成为了一些瓶颈,所以我想改变数据提取的工作方式,而不会失去太多的灵活性。

由于在任何一种情况下,最后的数据都必须转换为字符串,因此我首先将删除多数据类型容器并在开始时进行转换。我还想减少数据描述标签(以前的映射键)的开销,因为一个子弹射的数据总是在同一个 oder 中。

我的最终结果应该是某种机制,允许用列标题填充一些结构,其中包含对静态 QStringList 的指针/引用,并使用字符串化值填充 anoter QStringlist。

但是我不想失去在同一地方

进行两个定义的可能性(我不想在一个地方和数据上创建一个标题列表

所以最终的代码应该类似于这样:

struct FeatureData{
  const QStringList* header;
  QStringList        data;
}

template <>
FeatureDataFill<A>(const A* o, FeatureData* io_fd){
  io_fd->add("integral", o->i);
  io_fd->add("time", o->t);
}
template <>
FeatureDataFill<B>(const B* o, FeatureData* io_fd){
  io_fd->add("fl", o->fl);
  io_fd->add("a",  &o->a);
  io_fd->add("text", o->str);
}
template <>
FeatureDataFill<C>(const C* o, FeatureData* io_fd){
  io_fd->add("cb", o->cb);
  io_fd->add("a", &o->a);
  io_fd->add("b", &o->b);
}

以下代码应生成以下输出:

A a = {2 ,3};
B b = {1.0, {4,5}, "hallo"};
C c = {true, {6,7}, {2.0, {8,9}, "bye"}};
FeatureData dataA;
FeatureDataFill(&a, &dataA);
// dataA.header ->  "integral", "time"
// dataA.data   ->  "2", "3"
FeatureData dataB;
FeatureDataFill(&b, &dataB);
// dataA.header ->  "fl", "a/integral", "a/time", "str"
// dataA.data   ->  "1.0", "4", "5", "hallo"
FeatureData dataC;
FeatureDataFill(&c, &dataC);
// dataA.header ->  cb "a/integral", "a/time", "b/fl", "b/a/integral", "b/a/time", "b/str"
// dataA.data   ->  "true", "6", "7", "2.0", "8", "9", "bye"

我不确定如何构建模板架构来实现这一点。我尝试了一下,但我没有想出特别的想法。我有一个是利用一些模板类,它提供了一个静态fillData(T* data)方法和一些insertValue(const char* key, QString value)方法指针。首次使用 fillData 方法后,应将insertValue指针重定向到忽略键的其他方法,因为标头列表是在第一次运行后建立的,每次都可以重用。为了进一步节省构造/破坏,此方法应该只获取一个 const char 指针而不是一个QString对象。

但我现在还没有了解全貌。拼图的某些部分总是不想匹配。

有没有人有额外的提示或更好的建议?

有一件事可能是性能杀手,那就是动态分配,在那里执行的其他操作相对无害。动态分配涉及的是QStringList和QString(以及代码第一版中的数据映射)。我想知道在第一个版本中使用 new 是否合理,以及 DataMap 在幕后工作的奇迹。不过,这可能不容易改变。

然后,您

已经提到您不想多次提取标头数据,因为它始终相同。此外,还有一件事将永远相同,那就是特定字段的数字索引!换句话说,您不需要值的映射,而只需要一个索引列表(我不确定这些QStringList中的哪一个)!考虑到这一点,代码应如下所示:

template<typename T>
void extract_header(StringList& l, T const& t, String prefix);
template<>
void extract_header<A>(StringList& l, A const& a, String prefix)
{
    l.add(prefix + "integral");
    l.add(prefix + "time");
}
template<>
void extract_header<B>(StringList& l, B const& b, String prefix)
{
    l.add(prefix + "fl");
    extract_header(l, b.a, prefix + "a/");
    l.add(prefix + "text");
}

类似地,提取实际值的函数:

template<typename T>
void extract_value(StringList& l, T const& t);
template<>
void extract_value<A>(StringList& l, A const& a)
{
    l.add(a.integral);
    l.add(a.time);
}
template<>
void extract_value<B>(StringList& l, B const& b)
{
    l.add(b.fl);
    extract_value(l, b.a);
    l.add(b.text);
}

如果编写这些结构的数组,则只需对数组的每个元素调用extract_header()一次并extract_value()

笔记:

  • 我没有保留你传球的风格。你对指针的使用实际上是非惯用C++。传递不能为 null 的对象时,请改用引用。
  • l.add()应该将值格式化为字符串并将其添加到列表中。我想缺少一些转换代码,但我希望你明白这一点。
  • 由于元素的数量是固定的,您也可以利用这一点。只需编写一个类似的函数,返回相应对象的大小。您可以使用它为字符串列表预分配内存。
  • 如果为 int 和 float 等基本类型添加专用化,则可以重写提取函数的专用化,以便仅递归调用这些函数。然后,您必须稍微调整前缀处理。
  • 您甚至可以编写模板代码,只访问数据的元素,然后处理数据的单独代码,类似于访问者模式。然后,您可以重用它来实现各种提取函数。优点是只定义一次数据结构,因此不能有一个提供四个字段的extract_header<B>和一个只写入三个值的extract_value<B>
  • 如果要写入磁盘的这些结构的大量数组,请不要先将它们转换为字符串列表,然后再写入它们。原因是这将需要所有内存将实际数据保存为字符串。相反,逐个转换和写入它们,这样可以保持较低的内存要求,并且还使内存更有可能仍在缓存中。