【问题标题】:Iterate through struct by variable name按变量名遍历结构
【发布时间】:2016-05-18 22:17:42
【问题描述】:

更新:6 个月后,我刚刚遇到了这个答案:Is it legal to index into a struct?: Answer by Slava。我认为这是比此处提供的任何解决方案更好的解决方案,因为绝对没有未定义的行为。希望这对下一个人有所帮助,因为对我来说已经太迟了。


在您评论告诉我使用数组或向量或任何形式的容器之前,这是一个硬道理,我不能。我知道,这将通过数组来解决,否则任何解决方案都非常“hacky”。我很想使用容器,但我绝对不能。

我是一家大型公司的中级开发人员,我们正在使用公司范围内的库通过以太网发送数据。为什么它不能支持数组/向量有多种原因,而是使用 POD 结构(普通旧数据 - 字符、浮点数、整数、布尔值)。我从一个浮点数组开始,我必须使用它来填充具有相同数量的浮点数的结构。由于这个库的目的是通过以太网发送消息,所以我只需要进行两次迭代——一次在发送上,一次在接收上。在所有其他时间,此数据都存储为数组。我知道 - 我应该序列化数组并按原样发送它们,但我再说一遍 - 我绝对不能。

我有一个float[1024],必须遍历数组并填充以下结构:

struct pseudovector
{
    float data1;
    float data2;
    float data3;
    ...
    float data1024;
}

我已经用BOOST_PP_REPEATBOOST_PP_SEQ_FOR_EACH_I 生成了这个结构,这样我就不必写出所有的1024 个浮点数,而且它提高了可维护性/可扩展性。

以同样的方式,我尝试通过预编译器## concatination (https://stackoverflow.com/a/29020943/2066079) 迭代结构,但由于这是在预编译器时完成的,它不能用于运行时获取/设置。

我已经研究过实现反射,例如 How can I add reflection to a C++ application?Ponder Library,但是这两种方法都要求您明确写出可以反射的每个项目。在这种情况下,我不妨创建一个 std::map<string, float> 并通过字符串/整数连接在 for 循环中进行迭代:

for(i=0;i<1024;i++)
{
    array[i] = map.get(std::string("data")+(i+1))
}

谁能推荐一个不需要我写出超过 1024 行代码的更简洁的解决方案?感谢您的帮助!

我再重复一遍 - 我绝对不能使用任何类型的数组/向量。

【问题讨论】:

  • 这似乎是反模式。你能把这个结构包装在一个联合中吗? union wrapper { float arr[1024]; pseudovector vec; };
  • 为什么不只是float[1024] fa; pseudovector pv; memcpy(fa,&amp;pv, sizeof(pv));
  • @c-smile 因为这是未定义的行为

标签: c++ reflection iterator iteration c-preprocessor


【解决方案1】:

这可能比您预期的要容易。首先,一些警告:

  1. 标准保证数组是连续的;也就是说,它们之间没有插入填充,并且数组本身与元素类型的对齐要求对齐。

  2. 结构体没有这样的限制;它们可以受到任意填充。但是,给定的实现(在给定的版本)将在所有翻译单元中以相同的方式执行此操作(否则,如何使用相同的结构定义跨翻译单元传递数据?)。这样做的通常方法是相当明智的,尤其是当结构包含单一类型的 only 成员时。对于这样的结构,对齐方式通常匹配成员的最大对齐方式,并且通常没有填充,因为所有成员都具有相同的对齐方式。

在您的情况下,您的 1024 个浮点数组和具有 1024 个浮点成员的结构几乎可以肯定具有完全相同的内存布局。这标准绝对不能保证,但是您的编译器可能会记录其结构布局规则,并且您始终可以在单元测试中断言大小和对齐方式匹配(您确实有单元测试,对吗?)

鉴于这些警告,您几乎可以肯定能够在两者之间简单地使用reinterpret_cast(或memcpy)。

【讨论】:

    【解决方案2】:

    您可以使用类型双关将结构视为一个数组。

    float array[1024] = { ... };
    pseudovector pv1;
    float *f = static_cast<float*>(static_cast<void*>(&pv1));
    for (int i = 0; i < 1024; i++) {
        f[i] = array[i];
    }
    

    【讨论】:

    • 只要你对未定义的行为没问题。
    • 既然他是通过以太网发送结构,他显然可以接受。几乎可以肯定,编写它的代码对连续数据做出了相同的假设。
    • 很好,谢谢!这是有效的,因为结构中的所有项目都是浮点数,对吧?如果有混合类型(即每个结构中的标头信息),这不能使用?
    • @Andrew 我承认我没有跟上所有 C++ 强制转换运算符的速度;什么是合适的?
    【解决方案3】:

    您可以使用预处理器元编程来创建您的数组。你可以这样做:

    #define ACCESSOR(z, n, type) &type::data ## n
    
    auto arr[] = {
        BOOST_PP_ENUM(1000, ACCESSOR, pseudovector)
    };
    

    很可能需要调整 ACCESSOR。在这里使用auto 可能也不合法。

    然后你做:

    auto val = (pv1.*arr)[4];
    

    等等……

    不需要 UB。

    【讨论】:

      【解决方案4】:

      使用Boost.Hana,如果您可以使用最新版本的 Clang 或 GCC(afaik,唯一支持的编译器)进行编译。

      请允许我引用教程中的 Introspection 部分:

      正如我们将在此处讨论的那样,静态自省是 程序在编译时检查对象的类型。其他 换句话说,它是一个与类型交互的编程接口 编译时。例如,你有没有想过检查一些 未知类型有一个名为 foo 的成员?或者也许在某个时候你有 需要迭代结构的成员吗?

      对于introspecting user-defined types,您必须使用 Hana 定义结构,但这与以其他方式定义结构没有太大区别:

      struct pseudovector {
         BOOST_HANA_DEFINE_STRUCT(pseudovector,
          (float, data1),
          (float, data2),
          …
        );
      };
      

      您应该能够轻松地修改生成当前结构所需的任何宏,以生成这个。

      这会向pseudovector 添加一个嵌套结构,该结构仅包含一个静态成员函数。它不会影响 POD 特性、大小或数据布局。

      然后你可以像这个例子一样遍历它:

      pseudovector pv;
      
      hana::for_each(pv, [](auto pair) {
        std::cout << hana::to<char const*>(hana::first(pair)) << ": "
                  << hana::second(pair) << std::endl;
      });
      

      这里,pair 的成员是一个hana 编译时字符串(成员名称)和值。如果您希望 lambda 接受两个参数(名称和值),请使用 hana::fuse

      hana::for_each(pv, hana::fuse([](auto name, auto member) {
        std::cout << hana::to<char const*>(name) << ": " << member << std::endl;
      }));
      

      【讨论】:

      • @Andrew:完成。 FWIW,当时我正在考虑通过引用文档来证明 Hana 做了我们想要的事情
      猜你喜欢
      • 2011-01-13
      • 1970-01-01
      • 2015-05-21
      • 1970-01-01
      • 1970-01-01
      • 2011-02-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多