像许多人一样,这些天我一直在尝试c++ 11带来的不同特性。我最喜欢的是“基于范围的for循环”。

我明白:

for(Type& v : a) { ... }

等价于:

for(auto iv = begin(a); iv != end(a); ++iv)
{
  Type& v = *iv;
  ...
}

begin()只是返回标准容器的a.begin()。

但是如果我想让我的自定义类型“基于范围的for循环”感知呢?

我应该专门化begin()和end()吗?

如果我的自定义类型属于命名空间xml,我应该定义xml::begin()或std::begin() ?

简而言之,这样做的指导方针是什么?


当前回答

Chris Redford的答案当然也适用于Qt容器。下面是一个调整(注意我返回了constBegin(),分别从const_iterator方法返回constEnd()):

class MyCustomClass{
    QList<MyCustomDatatype> data_;
public:    
    // ctors,dtor, methods here...

    QList<MyCustomDatatype>::iterator begin() { return data_.begin(); }
    QList<MyCustomDatatype>::iterator end() { return data_.end(); }
    QList<MyCustomDatatype>::const_iterator begin() const{ return data_.constBegin(); }
    QList<MyCustomDatatype>::const_iterator end() const{ return data_.constEnd(); }
};

其他回答

我写下我的答案是因为有些人可能更喜欢简单的现实生活的例子,没有STL包含。

出于某种原因,我有自己的纯数据数组实现,我想使用基于范围的for循环。以下是我的解决方案:

template <typename DataType>
class PodArray {
public:
    class iterator {
    public:
        iterator(DataType * ptr): ptr(ptr){}
        iterator operator++() { ++ptr; return *this; }
        bool operator!=(const iterator & other) const { return ptr != other.ptr;  }
        const DataType& operator*() const { return *ptr; }
    private:
        DataType* ptr;
    };
private:
   unsigned len;
   DataType *val;
public:
   iterator begin() const { return iterator(val); }
   iterator end() const { return iterator(val + len); }
 
   // rest of the container definition not related to the question ...
};

然后是用法示例:

PodArray<char> array;
// fill up array in some way
for(auto& c : array)
    printf("char: %c\n", c);

受到BitTickler关于如何使它适用于非“容器”类型的评论的启发,这里有一个适用于双精度对象的最小示例:

class dranged {
    double start, stop, step, cur;
    int index;

public:
    dranged(double start, double stop, double step) :
        start(start), stop(stop), step(step),
        cur(start), index(0) {}

    auto begin() { return *this; }
    auto end() { return *this; }

    double operator*() const { return cur; }

    auto& operator++() {
        index += 1;
        cur = start + step * index;
        return *this;
    }

    bool operator!=(const dranged &rhs) const {
        return cur < rhs.stop;
    }
};

注意,在!=操作符中使用<保持了正确的不变量,但显然假设step是正的,并且不适用于更一般的范围。我使用整数索引来防止浮点错误的传播,但在其他方面力求简单。

这可以用于:

double sum() {
    double accum = 0;
    for (auto val : dranged(0, 6.28, 0.1)) {
        accum += val;
    }
    return accum;
}

GCC和Clang在进行优化编译时都能生成非常合理的代码(例如,GCC的-Os或高于-O1, Clang的-O2)。

如果你想直接用std::vector或std::map成员来支持一个类的迭代,下面是代码:

#include <iostream>
using std::cout;
using std::endl;
#include <string>
using std::string;
#include <vector>
using std::vector;
#include <map>
using std::map;


/////////////////////////////////////////////////////
/// classes
/////////////////////////////////////////////////////

class VectorValues {
private:
    vector<int> v = vector<int>(10);

public:
    vector<int>::iterator begin(){
        return v.begin();
    }
    vector<int>::iterator end(){
        return v.end();
    }
    vector<int>::const_iterator begin() const {
        return v.begin();
    }
    vector<int>::const_iterator end() const {
        return v.end();
    }
};

class MapValues {
private:
    map<string,int> v;

public:
    map<string,int>::iterator begin(){
        return v.begin();
    }
    map<string,int>::iterator end(){
        return v.end();
    }
    map<string,int>::const_iterator begin() const {
        return v.begin();
    }
    map<string,int>::const_iterator end() const {
        return v.end();
    }

    const int& operator[](string key) const {
        return v.at(key);
    }
    int& operator[](string key) {
        return v[key];
    } 
};


/////////////////////////////////////////////////////
/// main
/////////////////////////////////////////////////////

int main() {
    // VectorValues
    VectorValues items;
    int i = 0;
    for(int& item : items) {
        item = i;
        i++;
    }
    for(int& item : items)
        cout << item << " ";
    cout << endl << endl;

    // MapValues
    MapValues m;
    m["a"] = 1;
    m["b"] = 2;
    m["c"] = 3;
    for(auto pair: m)
        cout << pair.first << " " << pair.second << endl;
}

在这里,我将分享一个创建自定义类型的最简单的例子,它将与“基于范围的for循环”一起工作:

#include<iostream>
using namespace std;

template<typename T, int sizeOfArray>
class MyCustomType
{
private:
    T *data;
    int indx;
public:
    MyCustomType(){
        data = new T[sizeOfArray];
        indx = -1;
    }
    ~MyCustomType(){
        delete []data;
    }
    void addData(T newVal){
        data[++indx] = newVal;
    }

    //write definition for begin() and end()
    //these two method will be used for "ranged based loop idiom"
    T* begin(){
        return &data[0];
    }
    T* end(){
        return  &data[sizeOfArray];
    }
};
int main()
{
    MyCustomType<double, 2> numberList;
    numberList.addData(20.25);
    numberList.addData(50.12);
    for(auto val: numberList){
        cout<<val<<endl;
    }
    return 0;
}

希望,这将有助于一些新手开发像我:p:) 谢谢你!

我想详细解释一下@Steve Jessop回答的一些部分,一开始我不明白。希望能有所帮助。

std::begin calls the begin() member function anyway, so if you only implement one of the above, then the results should be the same no matter which one you choose. That's the same results for ranged-based for loops, and also the same result for mere mortal code that doesn't have its own magical name resolution rules so just does using std::begin; followed by an unqualified call to begin(a). If you implement the member functions and the ADL functions, though, then range-based for loops should call the member functions, whereas mere mortals will call the ADL functions. Best make sure they do the same thing in that case!


https://en.cppreference.com/w/cpp/language/range-for:

如果…… 如果range_expression是类类型C的表达式,该表达式同时具有一个名为begin的成员和一个名为end的成员 ),则begin_expr为 __range.begin()和end_expr是__range.end(); 否则,begin_expr是begin(__range), end_expr是end(__range),它们是通过依赖参数的查找(非adl)找到的 不执行查找)。

对于基于范围的For循环,首先选择成员函数。

但对于

using std::begin;
begin(instance);

首先选择ADL函数。


例子:

#include <iostream>
#include <string>
using std::cout;
using std::endl;

namespace Foo{
    struct A{
        //member function version
        int* begin(){
            cout << "111";
            int* p = new int(3);  //leak I know, for simplicity
            return p;
        }
        int *end(){
            cout << "111";
            int* p = new int(4);
            return p;
        }
    };

    //ADL version

    int* begin(A a){
        cout << "222";
        int* p = new int(5);
        return p;
    }

    int* end(A a){
        cout << "222";
        int* p = new int(6);
        return p;
    }

}

int main(int argc, char *args[]){
//    Uncomment only one of two code sections below for each trial

//    Foo::A a;
//    using std::begin;
//    begin(a);  //ADL version are selected. If comment out ADL version, then member functions are called.


//      Foo::A a;
//      for(auto s: a){  //member functions are selected. If comment out member functions, then ADL are called.
//      }
}