C ++ 11에서 비 멤버 시작 및 종료 함수를 사용하는 이유는 무엇입니까?


197

모든 표준 컨테이너에는 해당 컨테이너의 반복자를 반환 하는 beginend메소드가 있습니다. 그러나 C ++ (11)이 분명히 발표했다 무료 기능을 호출 std::begin하고 std::end이는 전화 beginend멤버 함수를. 그래서 글 쓰는 대신

auto i = v.begin();
auto e = v.end();

당신은 쓸 것입니다

auto i = std::begin(v);
auto e = std::end(v);

Herb Sutter는 자신의 대화 Modern Modern C ++ 에서 컨테이너의 시작 또는 종료 반복자를 원할 때 항상 무료 기능을 사용해야한다고 말합니다. 그러나 그는 당신이 원하는지 자세히 설명하지 않습니다 . 코드를 보면 하나의 문자가 모두 저장됩니다. 따라서 표준 컨테이너의 경우 무료 기능은 완전히 쓸모없는 것처럼 보입니다. Herb Sutter는 비표준 컨테이너에는 이점이 있지만 다시 한 번 자세하게 설명하지 않았습니다.

따라서 문제는 무료 함수 버전의 정확한 기능 std::beginstd::end해당 멤버 함수 버전을 호출하는 것 이상의 기능에 대한 것입니다. 왜 그 기능을 사용 하시겠습니까?


29
그것은 당신의 아이들을위한 그 점 저장 한 적은 캐릭터의 : xkcd.com/297
HostileFork은 그나마 신뢰 SE 말한다

나는 std::항상 반복해야하기 때문에 어떻게 든 사용하는 것을 싫어했습니다 .
Michael Chourdakis

답변:


162

당신은 어떻게 부르죠 .begin().end()는 C 배열에?

자유 기능은 나중에 변경할 수없는 데이터 구조에서 추가 할 수 있으므로보다 일반적인 프로그래밍이 가능합니다.


7
@JonathanMDavis : 템플릿 프로그래밍 트릭을 사용하여 end정적으로 선언 된 배열 ( int foo[5])을 가질 수 있습니다 . 포인터로 부패되면 운이 나빠질 것입니다.
Matthieu M.

33
template<typename T, size_t N> T* end(T (&a)[N]) { return a + N; }

6
@JonathanMDavis : 다른 사람이 지적한 바와 같이, 그것을 얻기 위해 확실히 가능 begin하고 end이미 포인터 자신에게 부패하지 않은 한 같은 C 배열에 - @Huw 그것을 밖으로 주문. 왜 당신이 원하는가 : 벡터를 사용하기 위해 배열을 사용하는 코드를 리팩터링했다고 상상해보십시오 (또는 그 반대의 이유도 있습니다). 당신이 사용하고 한 경우 beginend, 아마도 일부 영리 typedeffing을 구현 코드는 전혀 변화가 없습니다 (아마도 형식 정의의 일부를 제외).
Karl Knechtel

31
@JonathanMDavis : 배열은 포인터가 아닙니다. 그리고 모든 사람을 위해 :이 유명한 혼란을 끝내기 위해 (일부) 포인터를 "부패 된 배열"이라고 언급하지 마십시오. 언어에는 그러한 용어가 없으며 실제로는 사용되지 않습니다. 포인터는 포인터이고 배열은 배열입니다. 배열은 첫 번째 요소에 대한 포인터로 암시 적으로 변환 될 수 있지만, 여전히 다른 요소와 구별되지 않는 일반적인 오래된 포인터입니다. 물론 포인터의 "종료"를 얻을 수 없으며 케이스가 닫힙니다.
GManNickG

5
글쎄, 배열 이외에 컨테이너와 같은 측면을 노출하는 많은 수의 API가 있습니다. 분명히 타사 API를 수정할 수는 없지만 이러한 독립형 시작 / 종료 기능을 쉽게 작성할 수 있습니다.
edA-qa mort-ora-y

35

클래스가 포함 된 라이브러리가있는 경우를 고려하십시오.

class SpecialArray;

그것은 두 가지 방법이 있습니다 :

int SpecialArray::arraySize();
int SpecialArray::valueAt(int);

이 클래스에서 상속하고 필요한 경우에 대한 메소드 begin()end()메소드 를 상속 해야하는 값을 반복합니다.

auto i = v.begin();
auto e = v.end();

하지만 항상 사용한다면

auto i = begin(v);
auto e = end(v);

당신은 이것을 할 수 있습니다 :

template <>
SpecialArrayIterator begin(SpecialArray & arr)
{
  return SpecialArrayIterator(&arr, 0);
}

template <>
SpecialArrayIterator end(SpecialArray & arr)
{
  return SpecialArrayIterator(&arr, arr.arraySize());
}

SpecialArrayIterator다음과 같은 곳 이 있습니다.

class SpecialArrayIterator
{
   SpecialArrayIterator(SpecialArray * p, int i)
    :index(i), parray(p)
   {
   }
   SpecialArrayIterator operator ++();
   SpecialArrayIterator operator --();
   SpecialArrayIterator operator ++(int);
   SpecialArrayIterator operator --(int);
   int operator *()
   {
     return parray->valueAt(index);
   }
   bool operator ==(SpecialArray &);
   // etc
private:
   SpecialArray *parray;
   int index;
   // etc
};

지금 ie법적으로 반복 사용할 수 있습니다 및 SpecialArray의 값으로 접근


8
template<>줄을 포함해서는 안됩니다 . 템플릿을 전문화하지 않고 새로운 함수 과부하를 선언하고 있습니다.
David Stone

33

beginendfree 함수를 사용하면 간접 계층이 추가됩니다. 일반적으로 유연성을 높이기 위해 수행됩니다.

이 경우 몇 가지 용도를 생각할 수 있습니다.

가장 명백한 용도는 C- 어레이 (c 포인터가 아님)입니다.

다른 하나는 부적합 컨테이너에서 표준 알고리즘을 사용하려고 할 때입니다 (즉, 컨테이너에 .begin()메소드 가 없음 ). 컨테이너를 고칠 수 없다고 가정하면 다음 가장 좋은 옵션은 begin함수 를 오버로드하는 것입니다. Herb는 항상 begin함수를 사용 하여 코드의 균일 성과 일관성을 향상시킬 것을 제안합니다 . 어떤 컨테이너가 메소드를 지원하고 어떤 컨테이너 begin가 기능을 필요로하는지 기억 하지 않아도됩니다 begin.

옆으로, 다음 C ++ rev는 D의 의사 멤버 표기법을 복사해야합니다 . 경우 a.foo(b,c,d)그것을 정의되어 있지 않은 대신하려고합니다 foo(a,b,c,d). 그것은 주제와 동사 순서를 선호하는 가난한 사람들을 돕기 위해 약간의 구문 설탕입니다.


5
의사 회원 표기법 C # 같은 외모 /. 넷 확장 방법 . 모든 기능과 마찬가지로 다양한 상황에서 유용하게 사용되어 '학대'하기 쉽습니다.
Gareth Wilson

5
의사 멤버 표기법은 Intellisense로 코딩하는 데 도움이됩니다. "a." 관련 동사를 보여주고, 목록을 암기하는 데 뇌의 힘을 덜어주고, 관련 API 함수를 발견하면 멤버가 아닌 함수를 클래스로 분류하지 않고도 기능 복제를 방지 할 수 있습니다.
매트 커티스

UFCS (Unified Function Call Syntax)라는 용어를 사용하는 C ++ 로의 제안이 있습니다.
underscore_d

17

질문에 대답하기 위해 기본적으로 무료 함수 begin () 및 end ()는 컨테이너의 멤버 .begin () 및 .end () 함수를 호출하는 것 이상을 수행하지 않습니다. 에서 <iterator>이 같은 표준 컨테이너 중 하나를 사용할 때 자동으로 포함, <vector>, <list>, 등, 당신이 얻을 :

template< class C > 
auto begin( C& c ) -> decltype(c.begin());
template< class C > 
auto begin( const C& c ) -> decltype(c.begin()); 

질문의 두 번째 부분은 자유 함수를 선호하는 이유는 멤버 함수를 어쨌든 호출하는 것입니다. 그것은 실제로 v예제 코드 에 어떤 종류의 객체 가 있는지에 달려 있습니다. v의 유형이 표준 컨테이너 유형 인 vector<T> v;경우, free 또는 member 함수를 사용하는지 여부는 중요하지 않으며 동일한 기능을 수행합니다. v다음 코드와 같이 객체 가 더 일반적인 경우 :

template <class T>
void foo(T& v) {
  auto i = v.begin();     
  auto e = v.end(); 
  for(; i != e; i++) { /* .. do something with i .. */ } 
}

그런 다음 멤버 함수를 사용하면 T = C 배열, C 문자열, 열거 형 등의 코드가 깨집니다. 비 멤버 함수를 사용하면 사람들이 쉽게 확장 할 수있는보다 일반적인 인터페이스를 광고합니다. 무료 기능 인터페이스를 사용하여 :

template <class T>
void foo(T& v) {
  auto i = begin(v);     
  auto e = end(v); 
  for(; i != e; i++) { /* .. do something with i .. */ } 
}

코드는 이제 T = C 배열 및 C 문자열과 함께 작동합니다. 이제 소량의 어댑터 코드를 작성하십시오.

enum class color { RED, GREEN, BLUE };
static color colors[]  = { color::RED, color::GREEN, color::BLUE };
color* begin(const color& c) { return begin(colors); }
color* end(const color& c)   { return end(colors); }

우리는 코드가 반복 가능한 열거 형과 호환되도록 할 수 있습니다. Herb의 주요 포인트는 멤버 함수를 사용하는 것만 큼 자유 함수를 사용하는 것이 쉽고, 코드에 C 시퀀스 유형과의 역 호환성 및 비 stl 시퀀스 유형 (및 미래의 STL 유형과의 호환성)을 제공한다는 것입니다. 다른 개발자에게 저렴한 비용으로.


좋은 예입니다. enum그래도 참고 로 또는 다른 기본 유형을 사용하지는 않습니다. 간접 복사보다 복사 비용이 저렴합니다.
underscore_d

6

하나의 장점 std::beginstd::end그들이 외부 클래스에 대한 표준 인터페이스를 구현하기위한 확장 점 역할을한다는 것입니다.

CustomContainer범위 기반 for 루프 또는 템플릿 함수와 함께 클래스 를 사용 하고 기대 .begin()하고 .end()메소드를 사용하려면 해당 메소드를 구현해야합니다.

클래스가 이러한 메소드를 제공하는 경우 문제가되지 않습니다. 그렇지 않은 경우 수정해야합니다 *.

예를 들어 외부 라이브러리, 특히 상업용 및 비공개 소스 라이브러리를 사용할 때 항상 가능한 것은 아닙니다.

이러한 상황에서, std::begin그리고 std::end하나는 클래스 자체를 수정하는 것이 아니라 무료 기능을 과부하없이 반복자 API를 제공 할 수 있기 때문에, 편리.

예 :count_if 반복자 쌍 대신 컨테이너를 취하는 함수 를 구현한다고 가정하십시오 . 이러한 코드는 다음과 같습니다.

template<typename ContainerType, typename PredicateType>
std::size_t count_if(const ContainerType& container, PredicateType&& predicate)
{
    using std::begin;
    using std::end;

    return std::count_if(begin(container), end(container),
                         std::forward<PredicateType&&>(predicate));
}

이제이 custom과 함께 사용 count_if하려는 클래스의 경우 해당 클래스를 수정하는 대신 두 개의 무료 함수 만 추가하면됩니다.

이제 C ++에는 ADL ( Argument Dependent Lookup) 이라는 메커니즘 이있어 이러한 접근 방식이 훨씬 유연 해집니다.

간단히 말해 ADL은 컴파일러가 규정되지 않은 함수 (예 : begin대신 네임 스페이스가없는 함수)를 해석 할 때 std::begin인수의 네임 스페이스에 선언 된 함수도 고려한다는 의미입니다. 예를 들면 다음과 같습니다.

namesapce some_lib
{
    // let's assume that CustomContainer stores elements sequentially,
    // and has data() and size() methods, but not begin() and end() methods:

    class CustomContainer
    {
        ...
    };
}

namespace some_lib
{    
    const Element* begin(const CustomContainer& c)
    {
        return c.data();
    }

    const Element* end(const CustomContainer& c)
    {
        return c.data() + c.size();
    }
}

// somewhere else:
CustomContainer c;
std::size_t n = count_if(c, somePredicate);

이 경우에는 그 자격을 갖춘 이름은 중요하지 않습니다 some_lib::beginsome_lib::end 이후 - CustomContainersome_lib::너무, 컴파일러에 그 오버로드를 사용합니다 count_if.

그것은 또한 필요에 대한 이유 using std::begin;using std::end;에서 count_if. 이것은 우리가 자격이 사용할 수 있도록 begin하고 end, 따라서 ADL을 허용 하고 컴파일러가 선택할 수 있도록 std::begin하고 std::end다른 대안을 찾을 수 없을 때.

우리는 쿠키를 먹고 쿠키를 가질 수 있습니다. 즉 컴파일러가 표준 쿠키로 대체 할 수있는 동안 begin/의 사용자 정의 구현을 제공 할 수있는 방법이 end있습니다.

몇 가지 참고 사항 :

  • 같은 이유로, 거기에 다른 유사한 기능은 다음과 같습니다 std::rbegin/를 rend, std::size하고 std::data.

  • 다른 답변에서 언급했듯이 std::버전에는 기본 배열에 대한 과부하가 있습니다. 유용하지만 위에서 설명한 내용의 특별한 경우입니다.

  • std::begin템플릿 코드를 작성할 때는 특히 템플릿을보다 일반적으로 사용하기 때문에 친구를 사용하는 것이 좋습니다. 템플릿이 아닌 경우 해당되는 경우 방법을 사용할 수도 있습니다.

추신 : 나는이 게시물이 거의 7 세라는 것을 알고 있습니다. 중복으로 표시된 질문에 대답하고 싶었고 여기에 ADL에 대한 답변이 없다는 것을 발견했기 때문에 나는 그것을 보았습니다.


좋은 답변, 특히 ADL이 다른 사람들과 마찬가지로 상상력을 발휘하는 대신 ADL을 명확하게 설명 합니다.
underscore_d

5

비 멤버 함수는 표준 컨테이너에 어떠한 이점도 제공하지 않지만이를 사용하면보다 일관되고 유연한 스타일이 적용됩니다. 언젠가 기존 비표준 컨테이너 클래스를 확장하려는 경우 기존 클래스의 정의를 변경하는 대신 사용 가능한 함수의 오버로드를 정의하는 것이 좋습니다. 표준 컨테이너가 아닌 컨테이너의 경우 매우 유용하며 항상 무료 기능을 사용하면 표준 컨테이너를 비표준 컨테이너로 쉽게 대체 할 수 있으며 기본 컨테이너 유형이 코드에 더 투명하므로 코드를보다 유연하게 만듭니다. 훨씬 다양한 컨테이너 구현을 지원합니다.

그러나 물론 이것은 항상 적절하게 가중치를 부여해야하며 과도한 추상화도 좋지 않습니다. 무료 기능을 사용하는 것은 그리 큰 지나치지 않지만 C ++ 03 코드와의 호환성을 손상시킵니다.이 C ++ 11의 어린 시절에도 여전히 문제가 될 수 있습니다.


3
C ++ 03에서는 boost::begin()/ 만 사용할 수 end()있으므로 실제 비 호환성이 없습니다 :)
Marc Mutz-mmutz

1
@ MarcMutz-mmutz 글쎄, 부스트 종속성은 항상 옵션이 아닙니다 (그리고 단지에만 사용된다면 상당히 과잉입니다 begin/end). 그래서 순수한 C ++ 03과의 비 호환성도 고려할 것입니다. 그러나 말했듯이 C ++ 11 (적어도 begin/end특히)이 어쨌든 점점 더 채택 되고 있기 때문에 다소 작고 (그리고 점점 작아짐) 비 호환성 입니다.
Christian Rau

0

궁극적으로 이점은 컨테이너에 구애받지 않도록 일반화 된 코드에 있습니다. std::vector코드 자체를 변경하지 않고, 배열 또는 범위 에서 작동 할 수 있습니다 .

또한 컨테이너, 비 소유 컨테이너까지도 멤버가 아닌 범위 기반 접근자를 사용하여 코드에 의해 불가지론 적으로 사용될 수 있도록 개조 할 수 있습니다.

자세한 내용은 여기 를 참조 하십시오 .

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.