像许多人一样,这些天我一直在尝试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() ?

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


当前回答

在这里,我将分享一个创建自定义类型的最简单的例子,它将与“基于范围的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:) 谢谢你!

其他回答

如果你想直接用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;
}

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(); }
};

受到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)。

我想我没有什么要解释的,因为答案已经说明了这一点。但我可能不得不引用标准(N4885)中的这句话:

(支撑。[远程]/1:(强调我的)

The range-based for statement for ( init-statement(opt) for-range-declaration : for-range-initializer ) statement(possibly curly-braced) is equivalent to: { // starts namespace scope of for-range-initializer    init-statement; (opt)    auto &&range = for-range-initializer ;    auto begin = begin-expr ;    auto end = end-expr ;   for ( ; begin != end; ++begin ) {       for-range-declaration = * begin ;       statement ;    } } // ends namespace scope of for-range-initializer where (1.1) if the for-range-initializer is an expression, it is regarded as if it were surrounded by parentheses (so that a comma operator cannot be reinterpreted as delimiting two init-declarators); (1.2) range, begin, and end are variables defined for exposition only; and (3.1) begin-expr and end-expr are determined as follows: (1.3.1) if the for-range-initializer is an expression of array type R, begin-expr and end-expr are range and range+N, respectively, where N is the array bound. If R is an array of unknown bound or an array of incomplete type, the program is ill-formed; (1.3.2) if the for-range-initializer is an expression of class type C, and [class.member.lookup] in the scope of C for the names begin and end each find at least one declaration, begin-expr and end-expr are range.begin() and range.end(), respectively; (1.3.3) otherwise, begin-expr and end-expr are begin(range) and end(range), respectively, where begin and end undergo argument-dependent lookup ([basic.lookup.argdep]).


请注意,字符串、数组和所有STL容器都是可迭代的数据结构,因此已经可以使用基于范围的for循环对它们进行迭代。为了使数据结构可迭代,它必须类似于现有的STL迭代器:

1-必须有begin和end方法对该结构进行操作,可以作为成员,也可以作为独立函数,并且返回结构的开始和结束的迭代器。

2-迭代器本身必须支持operator*()方法、operator !=()方法和operator++(void)方法,可以作为成员方法,也可以作为独立函数。


#include <iostream>
#include <vector>
#define print(me) std::cout << me << std::endl

template <class T>
struct iterator
{
    iterator(T* ptr) : m_ptr(ptr) {};
    bool operator!=(const iterator& end) const { return (m_ptr != end.m_ptr); }
    T operator*() const { return *m_ptr; }
    const iterator& operator++()
    {
        ++m_ptr;
        return *this;
    }

private:
    T* m_ptr;
};

template <class T, size_t N>
struct array
{
    typedef iterator<T> iterator;

    array(std::initializer_list<T> lst)
    {

        m_ptr = new T[N]{};
        std::copy(lst.begin(), lst.end(), m_ptr);
    };

    iterator begin() const { return iterator(m_ptr); }
    iterator end() const { return iterator(m_ptr + N); }

    ~array() { delete[] m_ptr; }

private:
    T* m_ptr;
};

int main()
{
    array<std::vector<std::string>, 2> str_vec{ {"First", "Second"}, {"Third", "Fourth"} };
    for(auto&& ref : str_vec)
        for (size_t i{}; i != ref.size(); i++) 
            print(ref.at(i));

      //auto &&range = str_vec;
      //auto begin = range.begin();
      //auto end = range.end();
      //for (; begin != end; ++begin)
      //{
         // auto&& ref = *begin;
         // for (size_t i{}; i != ref.size(); i++) 
         //     print(ref.at(i));
      //}
}

这个程序的输出是:

第一个 第二个 第三 第四

我想详细解释一下@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.
//      }
}