【问题标题】:Arbitrary Object Member Selection C++任意对象成员选择 C++
【发布时间】:2014-05-03 08:41:50
【问题描述】:

我正在实现以下数据集,并希望避免通常涉及的冗长操作符号,例如 . 和 -> 选择器等。

struct myList {
    double node1 = 2,
    node2 = 4,
    node3 = 6.6,
    node4 = 8
};

如何任意对非特定选择的节点求和。例如对于 jQuery,如果这个结构是 JSON,那么我可以这样做:

$(myList).children().each(function() {
    sum += Number($(this).val());
});


// Or more generic, given size..
for (var i = 0; i < NUM_MEM; i++) {
    sum += $(myList).nth-child(i).val();
}

【问题讨论】:

  • 有一个std::vector&lt;double&gt; 而不是单个成员变量。 注意:c++没有运行时构造符号名的功能!
  • @πάνταῥεῖ 虽然我正在寻求简短的语法,但我也尽可能避免使用数组和向量以及非标准 f()。
  • 见Boost.Fusion(也许?)。
  • @πάνταῥεῖ 虽然不支持隐式创建符号,但如果不是递归然后通过迭代,必须有一种方法来投射和值?
  • @Jim22150 这是不可能的。与 Java 或 C# 不同,C++ 没有反射机制。它是一种静态类型语言。

标签: jquery c++ json struct


【解决方案1】:

使用数组、向量(或列表)或地图,具体取决于最实用的内容。

数组在编译时是不灵活的、固定大小的。 std::vector&lt;double&gt; 灵活且易于使用。例如:

#include <iostream>
#include <numeric>  // for std::accumulate, also look at <algorithms>
#include <vector>

struct data {
    std::vector<double> nodes;
};

int main() {
    data d = {{1, 2, 3}};
    double sum = std::accumulate(std::begin(d.nodes), std::end(d.nodes), 0);
    std::cout << "Total: " << sum << std::endl;
}

如果您希望使用名称而不是索引,请使用 std::map&lt;std::string, double&gt;(或 std::unordered_map)。

#include <iostream>
#include <map>
#include <numeric>
#include <string>

struct data {
    std::map<std::string, double> items;
};

typedef std::map<std::string, double>::value_type my_map_element;

int main() {
    data d = {{{"item1", 1}, {"item2", 2}, {"item3", 42}}};
    double sum = std::accumulate(std::begin(d.items), std::end(d.items), 0,
            [](double partial, my_map_element const& elt) {
                  return partial+elt.second;
            });
            //[](auto partial, auto const& elt) { // C++14
            //      return partial+elt.second;
            //});
    std::cout << "Total: " << sum << std::endl;
}

std::accumulate 与大多数标准算法一样,采用迭代器。您会在通用 C++ 库中到处找到它。这使得算法非常灵活——它们不依赖于所使用的实际数据结构。

如果您不能更改数据类,您可以使用简单的包装器使它们可迭代(无论如何一次一种类型),使用 std::reference_wrapper 的行为非常类似于引用,但可以存储在集合(和数组)。

这是一个如何工作的示例:

#include <functional> // for std::reference_wrapper
#include <iostream>
#include <numeric>    // for std::accumulate
#include <vector>

struct data
{
    double node1, node2, node3;
};

int main()
{
    typedef std::vector<std::reference_wrapper<double>> double_wrapper;

    data d { 1, 2, 3 };
    double_wrapper helper = { d.node1, d.node2, d.node3 };

    // You update via the helper or directly
    d.node1 = 4;
    helper[1].get() = 5; // will update d.node2

    // Handy iteration
    for (auto& node: helper)
        node.get()++;

    // And use standard algorithms
    std::cout
            << "Sum: "
            << std::accumulate(std::begin(helper), std::end(helper), 0)
            << std::endl;
}

警告:帮助器/包装器的生命周期与数据对象本身的生命周期密切相关。不要让包装器比数据对象更长寿。如果您将外部数据和类似上述向量的内容“重新打包”到结构中以供内部使用,出于同样的原因,还要非常小心复制(和移动)语义。

【讨论】:

  • @Jim22150:通常 C++ 库(包括标准库)中的大多数通用算法都在迭代器上运行,而不是在容器本身上运行。使它们更加灵活/强大。
  • 感谢您的帖子和您的解释。起初理解对象成员不能以迭代方式引用是很奇怪的。我已经被 ECMAscript 宠坏了,但我开始欣赏 C++ 所涉及的额外工作。
  • 你为什么不这样做:double arr[4] = {myList.node1, myList.node2, myList.node3, myList.node4 }; ? 从而保持结构不变。
  • 你需要一个引用数组才能正常工作(即设置数组会改变节点),而这在 C++ 中是不可能的。还使您的存储需求翻倍。可能会反过来工作(让您的节点成为对数组的引用)。
  • @Jim22150:为无法更改数据表示的情况添加了一个想法。 (另一种是在内部使用您自己的类,并将转换函数写入外部数据类型/从外部数据类型写入。样板代码,但您可能能够生成它。)
【解决方案2】:

只要您保持所有类型相同,所有结构访问说明符相同,没有虚函数并且所有类型都是离散类型,标准就可以保证这一点。

这有点恐怖,但反省总是……

#include <iostream>
#include <vector>
#include <string>
#include <algorithm>

struct myList {
    double node1 = 2,
    node2 = 4,
    node3 = 6.6,
    node4 = 8,
    myListEnd = -1;

    const double* nthChild(size_t i) const {
        return &node1 + i;
    }

    const double* begin() const {
        return &node1;
    }

    const double* end() const {
        return &myListEnd;
    }
};

struct deref {

    deref& operator+=(double a) {
        _tot += a;
        return *this;
    }
    operator double() const { return _tot; }
    double _tot = 0;
};

deref operator+(deref d, double a) {
    d += a;
    return d;
}


using namespace std;


int main()
{
    myList mine;

    auto sum = accumulate(&mine.node1, &mine.myListEnd, deref());
    cout << "sum is " << sum << endl; 

    sum = accumulate(mine.begin(), mine.end(), deref());
    cout << "sum is " << sum << endl; 

    sum = accumulate(mine.nthChild(1), mine.nthChild(3), deref());
    cout << "slice sum is " << sum << endl; 


   return 0;
}

输出:

Compiling the source code....
$g++ -std=c++11 main.cpp -o demo -lm -pthread -lgmpxx -lgmp -lreadline 2>&1

Executing the program....
$demo 
sum is 20.6
sum is 20.6
slice sum is 10.6

【讨论】:

  • 指针算法只保证在数组上工作。指向非数组的指针被视为指向 1 宽数组的第一个元素的指针。学究式地,超出此范围的访问是未定义的行为。 (所以不,标准并不能保证你上面的内容会起作用。)
  • 保证来自规则,即普通类必须具有与 c 结构相同的布局,这是可预测的(我们在 c 中使用它来将 IO 寄存器映射到结构上)。
猜你喜欢
  • 1970-01-01
  • 2011-01-27
  • 2011-01-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-11-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多