모든 <algorithm> 함수가 컨테이너가 아닌 범위 만 사용하는 이유는 무엇입니까?


49

에 유용한 함수가 많이 <algorithm>있지만, 모두 "시퀀스"-반복자 쌍에서 작동합니다. 예를 들어 컨테이너가 있고 컨테이너를 실행하고 싶다면 다음과 같이 std::accumulate작성해야합니다.

std::vector<int> myContainer = ...;
int sum = std::accumulate(myContainer.begin(), myContainer.end(), 0);

내가 할 모든 것은 :

int sum = std::accumulate(myContainer, 0);

내 눈에는 좀 더 읽기 쉽고 명확합니다.

이제 컨테이너의 일부에서만 작업하려는 경우가 있으므로 범위를 전달 하는 옵션 을 사용하는 것이 좋습니다 . 그러나 적어도 내 경험상, 이것은 드문 특별한 경우입니다. 나는 보통 전체 컨테이너에서 작동하고 싶습니다.

이 컨테이너를 취하고 호출하는 래퍼 함수 쓰기 쉽게 begin()하고 end()거기에 있지만, 이러한 편의 기능은 표준 라이브러리에 포함되지 않습니다.

이 STL 디자인 선택의 이유를 알고 싶습니다.


7
STL은 일반적으로 편의 래퍼를 제공합니까, 아니면 이전 C ++의 도구-지금------촬영-발자국 정책을 준수합니까?
Kilian Foth

2
레코드 : 자신의 래퍼를 작성하는 대신 Boost.Range에서 알고리즘 래퍼를 사용해야합니다. 이 경우boost::accumulate
ecatmur

답변:


40

... 범위를 전달하는 옵션을 갖는 것이 확실히 유용합니다. 그러나 적어도 내 경험상, 이것은 드문 특별한 경우입니다. 나는 보통 전체 컨테이너에서 작동하고 싶습니다

경험상 드문 특별한 경우 일 수 있지만 실제로 전체 컨테이너 는 특별한 경우이며 임의의 범위 는 일반적인 경우입니다.

현재 인터페이스를 사용하여 전체 컨테이너 사례를 구현할 수 있지만 대화를 수행 할 수는 없습니다.

따라서 라이브러리 작성자는 두 개의 인터페이스를 미리 구현하거나 모든 경우를 다루는 인터페이스 만 구현할 수 있습니다.


컨테이너를 가져 와서 begin () 및 end ()를 호출하는 래퍼 함수를 ​​작성하는 것은 쉽지만 이러한 편리한 함수는 표준 라이브러리에 포함되어 있지 않습니다.

특히 무료로 기능하기 때문에, 사실 std::beginstd::end지금 포함되어 있습니다.

따라서 라이브러리가 편리한 과부하를 제공한다고 가정 해 봅시다.

template <typename Container>
void sort(Container &c) {
  sort(begin(c), end(c));
}

이제 비교 functor를 사용하는 동등한 과부하를 제공해야하며 다른 모든 알고리즘에 해당하는 과부하를 제공해야합니다.

그러나 최소한 컨테이너가 가득한 상태에서 작업하고 싶은 모든 경우를 다루었습니다. 글쎄요 치다

std::for_each(c.rbegin(), c.rend(), foo);

컨테이너에서 거꾸로 작업을 처리 하려면 기존 알고리즘마다 다른 방법 (또는 방법 쌍)이 필요 합니다.


따라서 범위 기반 접근 방식은 다음과 같은 단순한 의미에서 더 일반적입니다.

  • 전체 컨테이너 버전이 할 수있는 모든 것을 할 수 있습니다
  • 전체 컨테이너 접근 방식은 필요한 과부하 수를 두 배 또는 세 배로 늘리는 동시에 여전히 덜 강력합니다.
  • 범위 기반 알고리즘도 작성 가능합니다 (이터레이터 어댑터를 스택 또는 체인으로 연결할 수 있지만 기능 언어 및 Python에서 더 일반적으로 수행됨)

다른 유효한 이유는 것을 어느 물론, 거기에 이미 STL과의 표준화를 얻을 수있는 많은 일이, 그리고 광범위하게 제한위원회 시간을 잘 사용되지 않을 것 사용 된 이전 편의 래퍼로 팽창. 관심이 있으시면 Stepanov & Lee의 기술 보고서를 여기 에서 찾을 수 있습니다.

의견에서 언급했듯이 Boost.Range 는 표준을 변경하지 않고도 새로운 접근 방식을 제공합니다.


9
OP가 포함 된 모든 사람이 모든 특별한 경우에 과부하를 추가 할 것을 제안하지 않는다고 생각합니다. "전체 컨테이너"가 "임의의 범위"보다 덜 일반적이지만 "전체 컨테이너, 반대로"보다 훨씬 일반적입니다. f(c.begin(), c.end(), ...)과부하 수를 두 배로 늘리지 않도록로 제한 하고 가장 일반적으로 사용되는 과부하 (단, 결정한 것)로 제한하십시오 . 또한 반복자 어댑터는 완전히 직교합니다 (알다시피, 파이썬에서 잘 작동합니다. 반복자는 매우 다르게 작동하며 이야기하는 대부분의 힘을 가지고 있지 않습니다).

3
전체 컨테이너에 동의하며 전달 사례는 매우 일반적이지만 제안 된 질문보다 가능한 사용의 하위 집합이 훨씬 작습니다. 특히 전체 컨테이너와 부분 컨테이너 사이가 아니라 전체 컨테이너와 부분 컨테이너 사이에서 선택이 가능하기 때문에 반전되거나 다르게 적용될 수 있습니다. 그리고 나는 그것이 제안하는 것이 공평하다고 생각 인지 당신은 또한 당신의 알고리즘 과부하를 변경해야하는 경우 어댑터를 사용의 복잡성이 더 크다.
쓸모없는

23
STL이 범위 객체를 제공 한 경우 컨테이너 버전 모든 경우를 포함 합니다. 예 std::sort(std::range(start, stop)).

3
반대로, 컴포저 블 기능 알고리즘 (예 : 맵 및 필터)은 컬렉션을 나타내는 단일 객체를 가져와 단일 객체를 반환하며, 반드시 한 쌍의 반복자와 유사한 것을 사용하지 않습니다.
svick

3
매크로는 이것을 할 수있다 : #define MAKE_RANGE(container) (container).begin(), (container).end()</ jk>
ratchet freak

21

이 주제에 대해 Herb Sutter기사가 있음이 밝혀졌습니다 . 기본적으로 문제는 과부하 모호성입니다. 다음과 같이 주어진다 :

template<typename Iter>
void sort( Iter, Iter ); // 1

template<typename Iter, typename Pred>
void sort( Iter, Iter, Pred ); // 2

그리고 다음을 추가하십시오 :

template<typename Container>
void sort( Container& ); // 3

template<typename Container, typename Pred>
void sort( Container&, Pred ); // 4

열심히 구별하는 것 41제대로.

제안되었지만 궁극적으로 C ++ 0x에 포함되지 않은 개념은이를 해결했으며을 사용하여 우회하는 것도 가능합니다 enable_if. 일부 알고리즘의 경우 전혀 문제가 없습니다. 그러나 그들은 그것에 반대하기로 결정했습니다.

이제 여기에 모든 의견과 답변을 읽은 후 range객체가 가장 좋은 해결책 이라고 생각 합니다. 나는 내가 볼 것이라고 생각한다 Boost.Range.


1
글쎄, 단지 a를 사용 typename Iter하면 엄격한 언어를 사용하기에는 오리 형식이 아닌 것 같습니다. 나는 ( 예를 들어 template<typename Container> void sort(typename Container::iterator, typename Container::iterator); // 1, template<template<class> Container, typename T> void sort( Container<T>&, std::function<bool(const T&)> ); // 4모호성 문제를 해결할 것이다) 등을 선호한다
Vlad

@Vlad : 불행히도, 이것은 T[]::iterator사용 가능한 배열이 없기 때문에 평범한 오래된 배열에서는 작동하지 않습니다 . 또한 적절한 반복자는 컬렉션의 중첩 유형이어야 할 의무가 없으며 정의하기에 충분합니다 std::iterator_traits.
firegurafiku

@ firegurafiku : 글쎄, 배열은 몇 가지 기본 TMP 트릭으로 특별합니다.
블라드

11

기본적으로 레거시 결정입니다. 반복자 개념은 포인터로 모델링되지만 컨테이너는 배열에서 모델링되지 않습니다. 또한 배열을 전달하기가 어렵 기 때문에 (일반적으로 길이에 대한 비 유형 템플릿 매개 변수가 필요함), 종종 함수에는 포인터 만 사용할 수 있습니다.

그러나 예, 후시에서는 결정이 잘못되었습니다. 우리는 begin/end또는로 구성 가능한 범위 객체를 사용하는 것이 좋습니다 begin/length. 이제 여러 _n접미사 알고리즘이 있습니다.


5

그것들을 추가하면 아무런 힘도 얻지 못할 것입니다 (당신은 이미 전화 .begin()하여 .end()직접 컨테이너 를 모두 할 수 있습니다). 적절하게 지정되고 공급 업체가 라이브러리에 추가하고 테스트하고 유지 관리 해야하는 라이브러리에 하나 더 추가 할 것입니다. 등

간단히 말해서, 전체 컨테이너 사용자가 하나의 추가 함수 호출 매개 변수를 입력하지 못하게하기 위해 추가 템플릿 세트를 유지 관리하는 데 어려움이 없기 때문에 아마도 없을 것입니다.


9
그것은 나에게 힘을 얻지 못할 것입니다.하지만 사실입니다. 그러나 결국에는 그렇지 std::getline않으며 여전히 라이브러리에 있습니다. 하나는 내가 만 사용하여 모든 것을 할 수 있기 때문에 확장 된 제어 구조는, 나에게 힘을 얻을하지 않는 것이 말할 정도로 멀리 갈 수 ifgoto. 예, 불공평 한 비교, 나는 안다;) 나는 사양 / 구현 / 유지 보수 부담을 어떻게 든 이해할 수 있다고 생각하지만, 우리가 여기서 말하는 것은 아주 작은 포장지 일 뿐이다.
치명적인 기타

작은 래퍼는 코딩하는 데 비용이 들지 않으며 라이브러리에있는 것이 의미가 없습니다.
ebasconp

-1

지금까지 http://en.wikipedia.org/wiki/C++11#Range-based_for_loop 는에 대한 훌륭한 대안 std::for_each입니다. 명시적인 반복자가 없는지 관찰하십시오.

int a[5] = {1, 2, 3, 4, 5};
for (auto &i: a) { i *= 2; }

( https://stackoverflow.com/a/694534/2097284에서 영감을 얻었습니다 .)


1
그것은 <algorithm>필요한 모든 실제 알고리즘 beginend반복자가 아니라의 단일 부분 만 해결 하지만 이점을 과대 평가할 수는 없습니다! 2009 년에 C ++ 03을 처음 시도했을 때 반복의 상용구로 인해 반복자에서 멀어졌으며 운 좋게도 내 프로젝트는이를 허용했습니다. 2014 년 C ++ 11에서 다시 시작한 것은 놀라운 업그레이드였으며, C ++ 언어는 항상 존재해야했으며 이제는 없이는 살 수 없습니다 auto &it: them:)
underscore_d
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.