IT story

"범위 기반 for 루프"에서 작동하도록 사용자 정의 유형을 만드는 방법은 무엇입니까?

hot-time 2020. 4. 18. 09:38
반응형

"범위 기반 for 루프"에서 작동하도록 사용자 정의 유형을 만드는 방법은 무엇입니까?


요즘 많은 사람들처럼 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(:)타입에 루프 를 적용하는 방법 X은 이제 두 가지 방법 중 하나입니다.

  • 멤버를 X::begin()만들고 X::end()반복자처럼 작동하는 것을 반환

  • 무료 함수를 작성 begin(X&)하고 end(X&)당신의 유형과 같은 네임 스페이스에, 반복자 같은 역할을 돌려 무언가를 X

그리고 유사점도 비슷합니다 const. 이것은 결함 보고서 변경을 구현하는 컴파일러와 그렇지 않은 컴파일러 모두에서 작동합니다.

반환 된 객체는 실제로 반복자 일 필요는 없습니다. for(:)루프는 C ++ 표준의 대부분의 지역 달리한다 뭔가 동등한로 확대 지정 :

for( range_declaration : range_expression )

된다 :

{
  auto && __range = range_expression ;
  for (auto __begin = begin_expr,
            __end = end_expr;
            __begin != __end; ++__begin) {
    range_declaration = *__begin;
    loop_statement
  }
}

로 시작하는 변수는 곳 __에서만 박람회이며, begin_expr그리고 end_expr마법입니다 전화 begin/ end

begin / end 반환 값에 대한 요구 사항은 간단합니다. pre-를 오버로드 ++하고, 초기화식이 유효한지, !=부울 컨텍스트에서 사용할 수있는 이진 , *할당 초기화 할 수있는 항목을 반환 하는 이진 range_declaration, 공개를 노출해야합니다. 파괴 장치.

반복자와 호환되지 않는 방식으로 그렇게하는 것은 아마도 나쁜 생각입니다 .C ++의 향후 반복은 코드를 깨는 것에 대해 상대적으로 무심 할 수 있기 때문입니다.

제쳐두고, 향후 표준 개정으로 인해 end_expr다른 유형을 반환 할 가능성이 상당히 높습니다 begin_expr. 이 기능은 손으로 쓴 C 루프만큼 효율적으로 최적화하기 쉬운 "게으른 끝"평가 (널 종료 감지와 같은) 및 기타 유사한 이점을 허용한다는 점에서 유용합니다.


¹ for(:)루프는 임시 auto&&변수를 변수 에 저장 하고이를 lvalue로 전달합니다. 임시 (또는 다른 rvalue)를 반복하는지 감지 할 수 없습니다. 이러한 과부하는 for(:)루프에 의해 호출되지 않습니다 . n4527의 [stmt.ranged] 1.2-1.3을 참조하십시오.

² 부르는 어느 begin/ 된 end방법 또는 ADL 전용 조회없는 기능 begin/ end, 또는 C 스타일 배열 지원 마법. 참고 std::begin않는 호출되지 않습니다 range_expression다시 표시의 유형의 개체 namespace std또는 동일에 의존.


에서 범위-대한 식 업데이트되었습니다

{
  auto && __range = range_expression ;
  auto __begin = begin_expr;
  auto __end = end_expr;
  for (;__begin != __end; ++__begin) {
    range_declaration = *__begin;
    loop_statement
  }
}

의 유형 __begin__end분리되었습니다.

이를 통해 종료 반복자가 시작과 동일한 유형이 될 수 없습니다. 엔드 이터레이터 유형은 !=시작 이터레이터 유형 만 지원하는 "센티넬"일 수 있습니다 .

이 유용한 이유의 실제 예는 끝 반복자가 읽기 "당신을 확인할 수 있다는 것입니다 char*그것을 가리키는 경우로 볼 '0'때" ==A를 char*. 이를 통해 C ++ 범위 표현식은 널 종료 char*버퍼에서 반복 할 때 최적의 코드를 생성 할 수 있습니다.

struct null_sentinal_t {
  template<class Rhs,
    std::enable_if_t<!std::is_same<Rhs, null_sentinal_t>{},int> =0
  >
  friend bool operator==(Rhs const& ptr, null_sentinal_t) {
    return !*ptr;
  }
  template<class Rhs,
    std::enable_if_t<!std::is_same<Rhs, null_sentinal_t>{},int> =0
  >
  friend bool operator!=(Rhs const& ptr, null_sentinal_t) {
    return !(ptr==null_sentinal_t{});
  }
  template<class Lhs,
    std::enable_if_t<!std::is_same<Lhs, null_sentinal_t>{},int> =0
  >
  friend bool operator==(null_sentinal_t, Lhs const& ptr) {
    return !*ptr;
  }
  template<class Lhs,
    std::enable_if_t<!std::is_same<Lhs, null_sentinal_t>{},int> =0
  >
  friend bool operator!=(null_sentinal_t, Lhs const& ptr) {
    return !(null_sentinal_t{}==ptr);
  }
  friend bool operator==(null_sentinal_t, null_sentinal_t) {
    return true;
  }
  friend bool operator!=(null_sentinal_t, null_sentinal_t) {
    return false;
  }
};

완전한 C ++ 17 지원이없는 컴파일러에서의 라이브 예제 ; for루프 수동 확장.


표준의 관련 부분은 6.5.4 / 1입니다.

_RangeT가 클래스 유형 인 경우, 클래스 멤버 액세스 검색 (3.4.5)에 의해 규정되지 않은 ID 시작 및 종료가 클래스 _RangeT의 범위에서 조회되고, 둘 중 하나 이상의 선언을 찾은 경우 시작하십시오. -expr 및 end-expr은 각각 __range.begin()__range.end()이며;

— 그렇지 않으면 begin-expr 및 end-expr은 각각 begin(__range)및이며 end(__range), begin 및 end는 인수 종속 조회 (3.4.2)로 조회됩니다. 이 이름 조회를 위해 네임 스페이스 std는 연결된 네임 스페이스입니다.

따라서 다음 중 하나를 수행 할 수 있습니다.

  • 정의 beginend멤버 함수
  • ADL에 의해 발견 될 함수 정의 beginend해제 (간단한 버전 : 클래스와 동일한 네임 스페이스에 배치)
  • 전문화 std::begin하고std::end

std::beginbegin()어쨌든 멤버 함수를 호출 하므로 위 중 하나만 구현하면 선택한 결과에 관계없이 결과가 동일해야합니다. 이는 원거리 기반 for 루프에 대해 동일한 결과이며, 고유 한 마법 이름 확인 규칙이없는 필사자 코드에 대한 동일한 결과이며 이에 using std::begin;대한 정규화되지 않은 호출 이어집니다 begin(a).

그러나 멤버 함수 ADL 함수 를 구현하는 경우 범위 기반 for 루프는 멤버 함수를 호출해야하지만 필사자 만 ADL 함수를 호출합니다. 이 경우에도 동일한 작업을 수행해야합니다.

작성하는 것이 컨테이너 인터페이스를 구현하는 경우 이미 컨테이너 인터페이스를 보유 begin()하고 있으며 end()멤버 기능이 이미 충분합니다. 컨테이너가 아닌 범위라면 (불변하거나 앞면의 크기를 모르는 경우 좋은 아이디어입니다) 자유롭게 선택할 수 있습니다.

배치 한 옵션 중 과부하가 걸리지 않아야합니다std::begin() . 사용자 정의 유형에 대한 표준 템플릿을 특수화 할 수 있지만 네임 스페이스 std에 정의를 추가하는 것은 정의되지 않은 동작입니다. 그러나 부분 함수 전문화가 부족하여 클래스 템플릿이 아닌 단일 클래스에 대해서만 할 수 있기 때문에 표준 함수를 전문화하는 것은 좋지 않은 선택입니다.


일부 사람들은 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);

begin ()과 end ()를 전문화해야합니까?

내가 아는 한 충분합니다. 또한 포인터를 증가 시키면 처음부터 끝까지 얻을 수 있어야합니다.

다음 예제는 시작과 끝의 const 버전이 누락되어 컴파일되고 잘 작동합니다.

#include <iostream>
#include <algorithm>

int i=0;

struct A
{
    A()
    {
        std::generate(&v[0], &v[10], [&i](){  return ++i;} );
    }
    int * begin()
    {
        return &v[0];
    }
    int * end()
    {
        return &v[10];
    }

    int v[10];
};

int main()
{
    A a;
    for( auto it : a )
    {
        std::cout << it << std::endl;
    }
}

다음은 함수로서 begin / end를 가진 또 다른 예입니다. 그들은 해야 하기 때문에 ADL의 클래스와 같은 공간에있을 :

#include <iostream>
#include <algorithm>


namespace foo{
int i=0;

struct A
{
    A()
    {
        std::generate(&v[0], &v[10], [&i](){  return ++i;} );
    }

    int v[10];
};

int *begin( A &v )
{
    return &v.v[0];
}
int *end( A &v )
{
    return &v.v[10];
}
} // namespace foo

int main()
{
    foo::A a;
    for( auto it : a )
    {
        std::cout << it << std::endl;
    }
}

클래스 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;
}

여기서는 " range-based for loop "에서 작동하는 커스텀 타입을 만드는 가장 간단한 예를 공유하고 있습니다 :

#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 :)
감사합니다.


Chris Redford의 답변은 Qt 컨테이너에서도 작동합니다 (물론). 다음은 적응입니다 ( const_iterator 메소드에서 constBegin()각각 a를 반환 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(); }
};

참고 URL : https://stackoverflow.com/questions/8164567/how-to-make-my-custom-type-to-work-with-range-based-for-loops

반응형