如何在 C++ 中创建一个迭代器只针对结构体向量中的一个字段

6

我有一个由所有基本类型组成的结构,就像这样:

struct record {
    int field1;
    double field2;
}

我有一个结构体实例的向量,就像这样:

vector<record> records;

是否有可能/什么是最好的方式来创建一个可以迭代field1vector<int>::iterator? 如果我使用了一个数组record records[n]呢?我需要类似于vector<int>::iterator的东西。

编辑:我需要的是一个vector<int>::iterator


@B-Brock,你是想用这个向量进行搜索吗? - Jonathan Mee
1
你为什么需要这样的迭代器?你是要将它传递给另一个函数吗?如果是,那么该函数的要求是什么(指针、随机访问迭代器、向量迭代器等)? - Ben Voigt
@BenVoigt 是的——完全正确。我想要传递给它的函数需要一个类型为vector<int>::iteratorbeginend迭代器。我已经在我的代码的其他部分中使用它,传递实际vector<int>的迭代器。 - Brian
它总是相同的“类型”数据 - 对我来说,int 表示时钟跳跃,所以我在考虑做一个继承的事情,但我不想创建一个只有 1 个字段的结构体,你知道吗? - Brian
@B-Brock:继承并不能解决问题...仅仅因为class X派生自struct Y并不意味着std::vector<X>std::vector<Y>之间有任何联系。 - Ben Voigt
显示剩余3条评论
5个回答

5

制作迭代器适配器

首先,最简单的解决方案是遍历容器并从迭代器中访问字段。

for (auto&& r : records) {
    int value = r.field1;
    /* do something with 'value' */
}

无论如何,如果您确实希望在解除引用时返回field1的迭代器,则可以轻松地实现一个迭代器适配器,该适配器派生自容器自己的迭代器。
struct my_it : public std::vector<record>::iterator {
    using std::vector<record>::iterator::iterator;
    int operator*() { return std::vector<record>::iterator::operator*().field1; }
};

并按照以下方式使用:

for (my_it it = std::begin(records); it != std::end(records); ++it) {
    int value = *it; // Dereferencing now returns 'field1'.
}

这是XY问题吗?

正如Ben Voigt所解释的那样,无法创建一个可以迭代连续数组中存储的类型为int以外元素的std::vector<int>::iterator

如果需要一个接受输入迭代器的函数,则应将其制作为模板函数。这样它就可以与任何容器类型的迭代器一起工作。这是标准库中所有算法实现的方式。

template <typename InputIt>
void func(InputIt first, InputIt last) {
    for (; first != last; ++first) {
        value = *it; // Dereferences input iterator of any type.
    }
}

迭代器应该通过它们的操作(即读取、增加、减少、随机访问),而不是它们的显式类型来进行接口化。迭代器按它们支持的操作数量进行分类。

例如,如果您需要在一个范围内进行迭代并一次性读取所有值,则需要使用输入迭代器作为参数。迭代器本身的类型应该是无关紧要的。

有关迭代器类别的更多信息,请参见此处


非常感谢您的建议!不过我需要它“看起来像”一个 vector<int>::iterator - Brian
我进行了编辑——我应该说是一个 vector<int>::iterator - Brian
这很有帮助,但我认为 using std::vector<record>::iterator::iterator; 在语法上是不正确的。迭代器没有定义::iterator。此外,我不得不为来自std :: vector <record> :: iterator的struct my_it定义构造函数才能使其工作。否则,我无法从std :: begin(records)创建my_it。类似于 my_it(std::vector<record>::iterator it) { *(std::vector<record>::iterator*)this = it; } - jonawebb

3

你没那么幸运。

vector<int>::iterator 不是多态的1。它没有地方可以进去改变指针步长。 vector<int>::iterator 只迭代一系列连续的整数对象,而你的整数对象没有被连续存储。

这就是为什么所有C++标准算法都是模板化的,接受任何类型的迭代器。如果将函数调整为接受任意迭代器类型的模板,则可以使用类似Snps编写的迭代器适配器。


1相对于指针算术,多态性是慢的,如果它没有类似于普通数组的性能,没有人会使用std::vector


1
你可以在原始的vector上使用lambda表达式。
例如:
for_each(records.begin(), records.end(), [](record& foo){/*operate on foo.field1 here*/});

请注意,绝大多数其他算法也接受lambda表达式,因此您可以使用lambda表达式迭代原始的vector,以访问仅field1。 我在这里有点自作聪明,但您寻找的行为似乎与map非常相似,因此您可能需要查看一下。 我想你喜欢Boost和邪恶,你也可以使用这个拼凑一些东西:http://www.boost.org/doc/libs/1_57_0/libs/range/doc/html/range/reference/adaptors/reference/strided.html

0

看起来您正在寻找类似于这样的东西

for(std::vector<record>::iterator i=records.begin(), end=records.end(); i!=end; ++i)
{
    std::cout << i->field1 << std::endl;
}

或者在C++11中

for(auto i=records.begin(), end=records.end(); i!=end; ++i)
{
    std::cout << i->field1 << std::endl;
}

0
如果你真的想要一个成员迭代器,你可以像这样做:
template <class M>
struct member_traits;

template <class T, class C>
struct member_traits<T C::*>
{
    using class_type = C;
    using return_type = T;
};

template <class Iterator, class C, class M>
struct member_iterator : Iterator
{
public:
    using Iterator::Iterator;

    template <class I, class Member>
    member_iterator(I&& begin, Member&& member)
        : std::vector<C>::iterator(std::forward<I>(begin))
        , member(std::forward<Member>(member))
    {
        static_assert(std::is_member_pointer<Member>::value,
                      "Member must be dereferenceable");
    }

    typename member_traits<M>::return_type& operator*()
    {
        return (*static_cast<Iterator&>(*this)).*member;
    }
private:
    M member;
};

template <class Member, class Iterator>
auto make_member_iterator(Member&& member, Iterator&& it)
    -> member_iterator<std::decay_t<Iterator>, typename member_traits<Member>::class_type, std::decay_t<Member>>
{
    return {std::forward<Iterator>(it), std::forward<Member>(member)};
}

struct Record
{
    int field1;
    double field2;
};

int main()
{
    std::vector<Record> v { {1, 1.0}, {2, 2.0}, {3, 3.0} };

    for (auto it = make_member_iterator(&Record::field1, v.begin()); it != v.end(); ++it)
    {
        std::cout << *it << " ";
    }
}

演示


1
使用基本调用语法,例如 return Iterator::operator*().*member;,会更加常规化,不是吗?或者您担心 operator* 可能是非成员函数? - Ben Voigt

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