범위 기반 'for'루프가 많은 간단한 알고리즘을 사용하지 않습니까?


81

알고리즘 솔루션 :

std::generate(numbers.begin(), numbers.end(), rand);

범위 기반 for 루프 솔루션 :

for (int& x : numbers) x = rand();

std::generateC ++ 11에서 범위 기반 for 루프에 대해 더 자세한 정보를 사용하려는 이유는 무엇 입니까?


14
구성 가능성? 마음을 결코 아, 반복자와 알고리즘 :( 없습니다 ... 작성 가능 어쨌든 종종
R. 마르틴 페르난데스

2
... 어떤되지 않습니다 begin()end()?
the_mandrill 2013 년

6
@jrok 나는 지금까지 많은 사람들이 range그들의 도구 상자에 기능 을 가질 것으로 기대 합니다. (예 for(auto& x : range(first, last)))
R. Martinho Fernandes

14
boost::generate(numbers, rand); // ♪
Xeo

5
@JamesBrock 우리는 C ++ 채팅방에서이 문제에 대해 자주 논의했습니다 (트랜 스크립트 어딘가에 있어야합니다 : P). 주된 문제는 알고리즘이 종종 하나의 반복기를 반환 하고 두 개의 반복기를 사용 한다는 것 입니다.
R. Martinho Fernandes 2013 년

답변:


79

첫 번째 버전

std::generate(numbers.begin(), numbers.end(), rand);

일련의 값을 생성하고자 함을 알려줍니다.

두 번째 버전에서는 독자가 스스로 알아 내야합니다.

입력 시간을 절약하는 것은 대개 읽기 시간에 손실되기 때문에 차선책입니다. 대부분의 코드는 입력 한 것보다 훨씬 더 많이 읽습니다.


13
입력 비용을 절약 하시겠습니까? 아, 알겠습니다. 왜 우리는 "컴파일 타임 온 전성 검사"와 "키보드의 키 누르기"에 대해 같은 용어를 사용합니까? :)
fredoverflow

25
" 타이핑 절약은 보통 차선책입니다. " 그것은 당신이 사용하는 라이브러리에 관한 것입니다. std :: generate는 numbers이유없이 두 번 지정해야하므로 깁니다 . 따라서 : boost::range::generate(numbers, rand);. 잘 구축 된 라이브러리에서 더 짧고 읽기 쉬운 코드를 모두 가질 수없는 이유는 없습니다.
Nicol Bolas 2013.01.10

9
그것은 모두 독자의 눈에 있습니다. For 루프 버전은 대부분의 프로그래밍 배경에서 이해할 수 있습니다. 컬렉션의 각 요소에 rand 값을 넣으십시오. Std :: generate는 최신 C ++를 알아야합니다. 또는 생성이 실제로 "생성 된 값 반환"이 아니라 "항목 수정"을 의미한다고 추측합니다.
하이드

2
컨테이너의 일부만 수정하려면 할 수 std::generate(number.begin(), numbers.begin()+3, rand)있지 않습니까? 그래서 number때때로 두 번 지정하는 것이 유용 할 수 있다고 생각합니다 .
Marson Mao 2013 년

7
@MarsonMao : 두 인수 만있는 경우 범위 의 일부를 유연하게 std::generate()지정할 수 있는 동시에 반복을 제거 std::generate(slice(number.begin(), 3), rand)하는 가상 범위 슬라이싱 구문을 사용하거나 더 잘 할 수 있습니다 . 세 가지 주장에서 시작하여 반대로하는 것은 더 지루합니다. std::generate(number[0:3], rand)numberstd::generate()
Lie Ryan

42

for 루프가 범위 기반인지 여부에 관계없이 전혀 차이가 없지만 괄호 안의 코드 만 단순화됩니다. 알고리즘은 의도 를 표시한다는 점에서 더 명확합니다 .


30

개인적으로 처음 읽은 내용 :

std::generate(numbers.begin(), numbers.end(), rand);

"우리는 범위의 모든 것에 할당하고 있습니다. 범위는 numbers 입니다. 할당 된 값은 무작위입니다."

나의 초기 독서 :

for (int& x : numbers) x = rand();

"우리는 범위 내의 모든 것에 무언가를하고 있습니다. 범위는 numbers 입니다. 우리가하는 일은 임의의 값을 할당하는 것입니다."

그것들은 상당히 비슷하지만 동일하지는 않습니다. 내가 첫 번째 독서를 유발하고 싶은 한 가지 그럴듯한 이유는이 코드에 대한 가장 중요한 사실이 범위에 할당된다는 것입니다. 그래서 당신의 "내가 왜 ..." generateC ++에서 std::generate"범위 할당"을 의미 하기 때문에 사용 합니다. btw가하는 것처럼 std::copy둘의 차이점은 당신이 할당하는 것입니다.

하지만 혼란스러운 요소가 있습니다. 범위 기반 for 루프는 numbers반복기 기반 알고리즘보다 범위가임을 본질적으로보다 직접적인 방식으로 표현합니다 . 이것이 사람들이 범위 기반 알고리즘 라이브러리에서 작업하는 이유 입니다. 버전 boost::range::generate(numbers, rand);보다 낫습니다 std::generate.

반대로 int&범위 기반 for 루프에는 주름이 있습니다. 범위의 값 유형이가 아닌 int경우 여기에서 변환 가능 여부에 의존하는 성가신 미묘한 작업을 수행하는 int&반면 generate코드 rand는 요소에 할당 할 수있는 결과에만 의존합니다 . 값 유형이라고 int해도 나는 그것이 아닌지 생각하기 위해 멈출 수 있습니다. 따라서 auto할당 된 내용을 볼 때까지 유형에 대한 생각을 연기 auto &x합니다. "어떤 유형이 있든지간에 범위 요소에 대한 참조를 가져옵니다"라고 말합니다. (그들이있는 거 기능 템플릿 때문에)이었다 돌아 가기 C ++ 03, 알고리즘 정확한 유형을 숨길 수있는 방법, 지금은있어 방법.

나는 항상 가장 단순한 알고리즘이 동등한 루프에 비해 미미한 이점만을 가지고있는 경우라고 생각합니다. 범위 기반 for 루프는 루프를 개선합니다. 따라서 여백이 더 좁아지고 특정 경우에 마음이 바뀔 수 있습니다. 하지만 여전히 스타일 차이가 있습니다.


?가있는 사용자 정의 유형을 본 적이 operator int&()있습니까? :)
fredoverflow

@FredOverflow는로 대체 int&되며 SomeClass&이제 변환 연산자와 표시되지 않은 단일 매개 변수 생성자에 대해 걱정해야합니다 explicit.
TemplateRex

@FredOverflow : 그렇게 생각하지 마십시오. 그렇기 때문에 그것이 발생하면 나는 그것을 기대하지 않을 것이고 내가 지금 그것에 대해 아무리 편집증 적이든간에 그것을 생각하지 않으면 나를 물릴 것입니다 ;-) 프록시 객체는 할 수 있습니다 operator int&()과 과부하로 작동 operator int const &() const하지만 다시과 로딩 operator int() const하여 작동 할 수 있습니다 operator=(int).
Steve Jessop 2013 년

1
@rhalbersma : 비 상수 참조는 임시 참조에 바인딩되지 않으므로 생성자에 대해 걱정할 필요가 없다고 생각합니다. 참조 유형에 대한 변환 연산자 일뿐입니다.
Steve Jessop

23

내 생각에, Effective STL Item 43 : "수기 루프보다 알고리즘 호출을 선호하십시오." 여전히 좋은 조언입니다.

나는 보통 begin()/ end()지옥을 제거하기 위해 래퍼 함수를 ​​작성 합니다. 그렇게하면 예제는 다음과 같습니다.

my_util::generate(numbers, rand);

나는 의도전달하고 가독성에서 모두 for 루프 기반 범위를 능가한다고 생각합니다 .


그렇긴하지만 C ++ 98에서 일부 STL 알고리즘 호출이 발화 할 수없는 코드를 생성했으며 "수작업으로 작성한 루프보다 알고리즘 호출 선호"를 따르는 것이 좋은 생각이 아닌 것 같음을 인정해야합니다. 다행히 람다가이를 변경했습니다.

Herb Sutter 의 다음 예제를 고려하십시오 . Lambdas, Lambdas Everywhere .

과제 : v의 첫 번째 요소 인 > xand를 찾습니다 < y.

람다없이 :

auto i = find_if( v.begin(), v.end(),
bind( logical_and<bool>(),
bind(greater<int>(), _1, x),
bind(less<int>(), _1, y) ) );

람다와 함께

auto i=find_if( v.begin(), v.end(), [=](int i) { return i > x && i < y; } );

1
질문에 약간 직교합니다. 첫 번째 문장 만이 질문을 다룹니다.
David Rodríguez-dribeas

@ DavidRodríguez-dribeas 예. 후반부는 항목 43이 여전히 좋은 조언이라고 생각하는 이유를 설명 하고 있습니다.
알리

Boost.Lambda를 사용하면 C ++ 람다 함수보다 훨씬 좋습니다. auto i = find_if (v.begin (), v.end (), _1> x && _1 <y);
sdkljhdf hda

1
래퍼는 +1입니다. 똑같이. 1 일 (또는 2 일)부터 표준에 포함되어야합니다.
Macke 2013

22

에서 상세를 줄일 수 있지만 의견, 수동 루프는, readabitly 부족 :

for (int& x : numbers) x = rand();

나는 초기화이 루프를 사용하지 않을 에 의해 정의 된 범위의 숫자를 내가 볼 때, 그것이 나에게 것 때문에, 이상 반복하는을 , 숫자의 범위,하지만 실제로는 (본질적으로)하지 않는 즉, 대신 범위에서 읽기 , 그것은 쓰는 .

를 사용하면 의도가 훨씬 더 명확 해 std::generate집니다.

1. 이 컨텍스트에서 초기화 는 컨테이너의 요소에 의미있는 값을 제공하는 것을 의미 합니다.


5
하지만 범위 기반 for 루프에 익숙하지 않기 때문이 아닙니까? 이 진술이 범위의 각 요소에 할당된다는 것이 나에게 상당히 분명해 보입니다. generate는 std::generateC ++ 프로그래머라고 가정 할 수있는 익숙한 경우에도 동일한 작업을 수행한다는 것이 분명합니다 (익숙하지 않은 경우 검색, 동일한 결과).
Steve Jessop 2013 년

4
@SteveJessop :이 답변은 다른 두 가지 답변과 다르지 않습니다. 독자의 노력이 조금 더 필요하고 오류가 발생하기 쉽습니다 (단일 &문자 를 잊어 버리면 어떨까요?). 알고리즘의 장점은 의도를 보여주는 반면 루프를 사용하면 추론해야한다는 것입니다. 루프 구현에 버그가있는 경우 버그인지 의도적 인 것인지 명확하지 않습니다.
David Rodríguez-dribeas

1
@ DavidRodríguez-dribeas :이 답변은 다른 두 가지 IMO와 크게 다릅니다. 그것은으로 드릴을 시도 이유는 저자가 다른 것보다 이해 더 명확 하나의 코드를 / 발견 한. 다른 사람들은 분석하지 않고 진술합니다. 그게 내가 :-) 그것에 응답이 하나의 흥미로운 충분히 찾을 이유
스티브 Jessop

1
@SteveJessop : 실제로 숫자를 생성 하고 있다는 결론을 내리기 위해 루프의 본문을 살펴 봐야 합니다.하지만 std::generate, 단순한보기만으로도이 함수에 의해 무언가 가 생성되고 있다고 말할 수 있습니다 . 함수에 대한 세 번째 인수로 무엇 에 대한 답을 얻을 수 있는지 . 나는 이것이 훨씬 낫다고 생각합니다.
Nawaz

1
@SteveJessop : 그래서 그것은 당신이 소수에 속한다는 것을 의미합니다. 나는 대다수에게 더 명확한 코드를 작성할 것입니다 : P. 마지막으로, 다른 사람들이 저와 같은 방식으로 루프를 읽을 것이라고 말하지 않았습니다. 나는 (오히려 말했다 이 나에게 오해의 소지가있는 루프를 읽을 수있는 하나의 방법이며, 루프 본문이 있기 때문에, 다른 프로그래머가 무슨 일이 일어나고 있는지 알아 내기 위해 다른 읽을 것); 그들은 다른 이유로 그러한 루프의 사용에 반대 할 수 있으며, 모두 그들의 인식에 따라 정확할 수 있습니다.
Nawaz 2013 년

9

반복기를 입력으로 취하는 알고리즘이 할 수있는 범위 기반 루프로는 (간단히) 할 수없는 일이 있습니다. 예를 들어std::generate 같습니다.

에 컨테이너 채워 limit제외 (를 limit유효한 반복자에이다numbers 를 한 분포의 변수로 나머지는 다른 분포의 변수 임) 채 .

std::generate(numbers.begin(), limit, rand1);
std::generate(limit, numbers.end(), rand2);

반복자 기반 알고리즘을 사용하면 작업중인 범위를 더 잘 제어 할 수 있습니다.


8
가독성 이유는 알고리즘을 선호 하는 거대한 이유이지만 범위 기반 for 루프알고리즘 의 하위 집합 일 뿐이 므로 어떤 것도 비난 할 수 없음 을 보여주는 유일한 답변입니다 ...
K-ballo

6

의 특정 사례 std::generate에 대해서는 가독성 / 의도 문제에 대한 이전 답변에 동의합니다. std :: generate는 나에게 더 명확한 버전으로 보입니다. 그러나 나는 이것이 맛의 문제임을 인정합니다.

즉, std :: algorithm을 버리지 않는 또 다른 이유가 있습니다. 일부 데이터 유형에 특화된 특정 알고리즘이 있습니다.

가장 간단한 예는 std::fill. 일반 버전은 제공된 범위에 대해 for 루프로 구현되며이 버전은 템플릿을 인스턴스화 할 때 사용됩니다. 하지만 항상 그런 것은 아닙니다. 예를 들어 범위를 제공 std::vector<int>하면 실제로 호출됩니다.memset 하여 훨씬 빠르고 더 나은 코드를 생성합니다.

그래서 저는 여기서 효율성 카드를 사용하려고합니다.

손으로 쓴 루프는 std :: algorithm 버전만큼 빠르지 만 거의 빠를 수 없습니다. 뿐만 아니라 std :: algorithm은 특정 컨테이너 및 유형에 특화 될 수 있으며 깨끗한 STL 인터페이스에서 수행됩니다.


3

내 대답은 아마도 아닐 것입니다. 우리가 C ++ 11에 대해 이야기하고 있다면 아마도 (아니요). 예를 들어 std::for_each람다와 함께 사용하는 것이 정말 짜증납니다.

std::for_each(c.begin(), c.end(), [&](ExactTypeOfContainedValue& x)
{
    // do stuff with x
});

그러나 범위 기반 for를 사용하는 것이 훨씬 좋습니다.

for (auto& x : c)
{
    // do stuff with x
}

반면에 C ++ 1y에 대해 이야기하고 있다면 아니요, 알고리즘은 범위 기반에 의해 폐기되지 않을 것이라고 주장합니다. C ++ 표준위원회에는 C ++에 범위를 추가하는 제안을 작업하는 연구 그룹이 있으며 다형성 람다에 대한 작업도 진행 중입니다. 범위는 반복기 쌍을 사용할 필요가 없으며 다형성 람다는 람다의 정확한 인수 유형을 지정하지 않도록합니다. 이것은 다음 std::for_each과 같이 사용될 수 있음을 의미합니다 (이것을 어려운 사실로 받아들이지 마십시오. 오늘 꿈이 어떻게 생겼는지입니다) :

std::for_each(c.range(), [](x)
{
    // do stuff with x
});

따라서 후자의 경우 알고리즘의 장점은 []람다 로 작성 하여 제로 캡처를 지정 한다는 것입니다 . 즉, 루프 본문을 작성하는 것과 비교하여 어휘 적으로 나타나는 변수 조회 컨텍스트에서 코드 덩어리를 분리했습니다. 분리는 일반적으로 독자에게 유용하며 읽는 동안 생각할 필요가 없습니다.
Steve Jessop 2013 년

1
점령은 요점이 아닙니다. 요점은 다형성 람다를 사용하면 x 유형이 무엇인지 명시 적으로 설명 할 필요가 없다는 것입니다.
sdkljhdf hda

1
이 경우이 가상의 C ++ 1y에서 for_each람다와 함께 사용하더라도 여전히 무의미한 것 같습니다 . foreach + capturing lambda는 현재 범위 기반 for 루프를 작성하는 장황한 방법이며, 루프보다 약간 덜 장황한 방법이됩니다. for_each물론 방어해야한다고 생각하는 것은 아니지만 답변을보기 전에도 질문자가 알고리즘을 이기고 싶다면 for_each가능한 모든 대상 중에서 가장 부드러운 대상을 선택할 수 있다고 생각했습니다 ;-)
Steve Jessop

방어 for_each하지는 않지만 범위 기반에 비해 한 가지 작은 장점이 있습니다. parallel_ 접두사를 붙여서 더 쉽게 병렬화 할 parallel_for_each수 있습니다 (PPL을 사용하는 경우 스레드로부터 안전하다고 가정). . :-D
sdkljhdf hda

@lego 당신의 "작은" 이점은 s의 구현이 인터페이스 뒤에 숨겨져 있고 임의로 복잡 할 수 있다는 사실까지 일반화하면 실제로 "큰" 이점입니다 std::algorithm(또는 임의로 최적화 됨).
Christian Rau

1

주목해야 할 한 가지는 알고리즘이 방법이 아니라 수행되는 작업을 표현한다는 것입니다.

범위 기반 루프에는 작업이 수행되는 방식이 포함됩니다. 첫 번째 요소부터 시작하여 적용하고 끝까지 다음 요소로 이동합니다. 단순한 알고리즘조차도 다른 방식으로 작업을 수행 할 수 있습니다 (적어도 끔찍한 벡터에 대해 생각하지 않고 특정 컨테이너에 대한 일부 과부하). 적어도 작업 방식은 작성자 업무가 아닙니다.

나에게 그것은 큰 차이이고, 가능한 한 많이 캡슐화하고, 가능한 한 문장을 정당화하는 알고리즘을 사용합니다.


1

범위 기반 for 루프가 바로 그것입니다. 물론 표준이 변경 될 때까지.

알고리즘은 함수입니다. 매개 변수에 몇 가지 요구 사항을 부여하는 함수입니다. 요구 사항은 사용 가능한 모든 실행 스레드를 활용하고 자동으로 속도를 높이는 구현을 허용하기 위해 표준에 명시되어 있습니다.

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