从对象向量中提取元素

6

给定一个对象向量,是否有一种优雅的方式来提取它的成员?我目前只是使用for循环,但如果有一种方法可以做到这一点就好了。例如:

#include <vector>

struct Object {
  int x;
  float y;
};

int main() {
  std::vector<Object> obj;
  // Fill up obj

  std::vector<int> all_x = obj.x; // Won't work obviously
}
5个回答

6
通过使用range-v3,这将变得非常简单。
std::vector<int> xs = objs | ranges::view::transform(&Object::x);

或者直接使用视图:
auto xs = objs | ranges::view::transform(&Object::x);

Demo


1
现在它已经标准化了 https://gcc.godbolt.org/z/WPzP8qexa - Ayxan Haqverdili

3
作为一种不支持协变聚合的数据类型,如std::vector(或者c++中的其他数据类型),没有一种语法上漂亮的方式来实现你想要的功能。
如果你真的想要用obj元素的x成员初始化all_x,那么你可以定义一个新的迭代器类,像这样:
class getx_iter : public vector<Object>::iterator
{
public:
    getx_iter(const vector<Object>::iterator &iter) : vector<Object>::iterator(iter) {}
    int operator*() { return (*this)->x; }
};

工作代码示例

如果您可以接受初始化一个空的vector然后填充它,使用lambda的std::transform是一个更清晰的选项(正如@andars建议的那样)。

您还可以通过使用vector::reserve()back_inserter来避免额外的初始化:

xs.reserve(foos.size());
std::transform(foos.begin(), foos.end(), back_inserter(xs), [](Foo f){return f.x;});

还要注意的是,虽然xObject的私有成员,并且没有getter方法,但提取它将非常困难。


2
我想不出一个很好的方法。
一种替代方法是使用带有lambda表达式的std::transform
#include <vector>
#include <algorithm>


class Foo {
public:
    Foo(int x_): x(x_) {}
    int x;
};

int main() {
    std::vector<Foo> foos;
    for (int i = 0; i<10; i++) {
        foos.push_back(Foo(i));
    }

    std::vector<int> xs;
    xs.resize(foos.size());
    std::transform(foos.begin(), foos.end(), xs.begin(), [](Foo f){return f.x;});
}

0
一些模板和宏的魔法,就可以实现它:
#include <vector>
#include <algorithm>
using namespace std;


class Foo {
public:
    Foo(int x_): x(x_) {}
    int x;
};

#define GETFIELD(type, field) [](const type & obj){return obj.field;}

template<typename T,typename U, typename TMapper>
void MapVector(vector<T>& src, vector<U>& dst, TMapper mapper) {
    for (const auto& it: src) {
        dst.push_back(mapper(it));
    }
}

#define MapVectorField(src, dst, field) MapVector(src, dst, GETFIELD(decltype(src)::value_type, field))

int main() {
    vector<Foo> vFoo;
    for (int i = 0; i < 10; i++) {
        vFoo.push_back(Foo(i));
    }

    vector<int> vX;
    MapVector(vFoo, vX, GETFIELD(Foo, x));
    MapVectorField(vFoo, vX, x);

    for (int i = 0; i < vX.size(); i++) {
        printf("%d\n", vX[i]);
    }
}

当然,记住将宏命名为MapVectorField并不是很好,因为它是函数,或者在生产中编写using namespace std

0
这里有另一个非常容易使用的宏。它需要程序员知道内部循环变量名称为“e”,即元素。如果您记住这一点,它将适用于字段或方法,以及普通对象或指向对象的指针。
该宏仅为:
#define map(vTarget, vSource, eField) \
            for (auto e: vSource) { vTarget.push_back(eField); } 

例如,假设我们有一个名为Person的类,它具有一个字符串名称字段和一个整数年龄字段,并且我们想要将仅名称提取到新向量中。然后,一个示例用法可能是:
map(names, people, e.name);

请注意第三个参数中的“e”。这是必需的,因为宏使用变量“e”来迭代向量中的元素。因此,请记住您需要使用哪种语法。我认为这些是4种情况:
  • e.field
  • e.method()
  • e->field
  • e->method()

让我们试试它们。这是一个完整的例子。嘿,如果有人有更优雅的连接解决方案,我全听着。

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

using namespace std;


#define map(vTarget, vSource, eField) \
            for (auto e: vSource) { vTarget.push_back(eField); } 



class Person {
    public:
        string name;
        int    age;

    public:
        Person(string name, int age) {
            this->name=name;
            this->age=age;
        }
        string& getName() {return name;}
        int     getAge()  {return age;}
};


string join(vector<string> vSource, const string separator) {
    string buf;
    bool first=true;

    for (string e: vSource) {
        if (first) {
            first=false;
        } else {
            buf+=separator;
        }
        buf+=e;
    }
    return buf;
}


int main(int argc, char **argv) {

// using a normal Object
    vector<Person> people;
    vector<string> names; 

    people.push_back(Person("john", 27));
    people.push_back(Person("jane", 26));


    names.clear();
    map(names, people, e.name);
    cout << join(names, ",") << endl;    

    names.clear();
    map(names, people, e.getName());
    cout << join(names, ",") << endl;    


// using a pointer to an object
    vector<Person*>  morePeople;
    morePeople.push_back(new Person("bob", 27));
    morePeople.push_back(new Person("amy", 26));

    names.clear();
    map(names, morePeople, e->name);
    cout << join(names, ",") << endl;    

    names.clear();
    map(names, morePeople, e->getName());
    cout << join(names, ",") << endl;    

}

示例输出:

john,jane
john,jane
bob,amy
bob,amy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接