【问题标题】:Can I iterate over class members as though they were an array in C++?我可以像 C++ 中的数组一样遍历类成员吗?
【发布时间】:2017-06-27 15:35:26
【问题描述】:

假设我有一个类似于以下的类:

struct Potato {
    Eigen::Vector3d position;
    double weight, size;
    string name;
};

还有Potatos的集合

std::vector<Potato> potato_farm = {potato1, potato2, ...};

这很明显是一个结构数组 (AoS) 布局,因为假设对于大多数用途,将所有 Potato 的数据集中在一起是有意义的。但是,我可能想计算最常见的名称,其中数组结构 (SoA) 设计使事物与具有名称的事物类型无关(所有人都有名字的数组,地点数组,都带有名称等)C++ 是否有任何工具或技巧可以使 AoS 布局看起来像 SoA 以执行此类操作,或者是否有更好的设计来完成相同的操作? p>

【问题讨论】:

  • 如何让它变得更容易?或者现在如何计算平均值并不容易?只需遍历您的potato_farm,访问权重,计算平均值,完成。当然,如果您的意思是内存效率更高,那么拥有数组结构可能会有优势,但我不明白不同“外观”的意义,因为获取平均权重的代码差别不大
  • @tobi303 与具有数组结构的 STL 算法交互要自然得多(每个数组都将具有 beginend 方法,它们可以原生地提供例如 std::accumulate 而无需写一大堆lambda函数来挖掘相关的成员变量)。
  • @ConnorGlosser 不过,您只需为std::accumulate 编写一个 lambda,不需要太多。使用您的 IMO 数组结构并不“容易”。
  • @ConnorGlosser 是的,我正在考虑,您必须编写一些样板文件才能使 std::accumulate 访问成员
  • 您的potato_farm 可以为每个成员提供beginend 迭代器。该迭代器简单地包装了向量迭代器,但在取消引用时返回适当的成员。然后你会保存 lambdas

标签: c++ arrays


【解决方案1】:

您可以使用 lambdas 访问算法中超出范围的特定成员:

double mean = std::accumulate( potato_farm.begin(), potato_farm.end(), 0.0, []( double val, const Potato &p ) { return val + p.weight; } ) / potato_farm.size();

如果这还不够,你不能让它看起来像数据数组,因为这需要对象在连续的内存中,但你可以让它像一个容器。因此,您可以实现自定义迭代器(例如 type == double 的随机访问迭代器,它对权重成员进行迭代)。 here 描述了如何实现自定义迭代器。您甚至可以将其设为通用,但尚不清楚这是否会更糟,因为正确实施并不是很简单。

【讨论】:

    【解决方案2】:

    不幸的是,没有语言工具可以通用地将结构更改为 SoA。当您尝试将 SIMD 编程提升到更高水平时,这实际上是一大障碍。

    您需要手动创建 SoA。但是,您可以通过创建对 SoA 对象的引用来帮助自己,就像它是一个普通的 Potato 一样。

    struct Potato {
        float position;
        double weight, size;
        std::string name;
    };
    struct PotatoSoARef {
        float& position;
        double& weight;
        double& size;
        std::string& name;
    };
    class PotatoSoA {
    private:
        float* position;
        double* weight;
        double* size;
        std::string* name;
    public:
        PotatoSoA(std::size_t size) { /* allocate the SoA */ }
        PotatoSoARef operator[](std::size_t idx) {
            return PotatoSoARef{position[idx], weight[idx], size[idx], name[idx]};
        }
    };
    

    这样,无论您是否拥有 Potatos 的 AoS 或 SoA,您都可以通过 arr[idx].position 等方式访问其字段(作为 r 值和 l 值)。编译器很可能会将代理优化掉。

    您可能还想添加其他构造函数和访问器。

    如果您希望函数具有适用于 AoS 和 SoA 访问模式的统一接口,您可能还对使用返回 PotatoSoARefoperator[] 实现常规 AoS 感兴趣。


    如果您愿意离开 C++,您可能会对语言扩展感兴趣,例如 Sierra

    【讨论】:

      【解决方案3】:

      正如 Slava 所说,如果不编写自己的迭代器,您将无法从 AoS 数据中获得类似 SoA 的访问权限,我认为真的很难考虑是否使用 STL 算法在这样做之前很重要,特别是如果这不是一个通用的解决方案。无论如何,SoA 数据的主要好处是缓存性能,而不是您正在使用的任何容器的特定语法,除了 实际的 SoA 数据之外没有任何东西可以为您提供。

      【讨论】:

        【解决方案4】:

        使用range-v3(不在C++17 :-/ 中),您可以使用投影或转换视图:

        ranges::accumulate(potato_farm, 0., ranges::v3::plus{}, &Potato::weight);
        

        auto weightsView = potato_farm | ranges::view::transform([](auto& p) { return p.weight; });
        ranges::accumulate(weightsView, 0.);
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-09-08
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多