수작업 루프보다 알고리즘을 선호하십니까?


10

다음 중 더 읽기 쉬운 것을 찾으십니까? 손으로 쓴 루프 :

for (std::vector<Foo>::const_iterator it = vec.begin(); it != vec.end(); ++it)
{
    bar.process(*it);
}

또는 알고리즘 호출 :

#include <algorithm>
#include <functional>

std::for_each(vec.begin(), vec.end(),
              std::bind1st(std::mem_fun_ref(&Bar::process), bar));

std::for_each그런 간단한 예제에 이미 너무 많은 코드가 필요하다는 점을 감안할 때 실제로 가치가 있는지 궁금합니다 .

이 문제에 대한 당신의 생각은 무엇입니까?


2
C ++의 경우 대답은 분명합니다. 그러나 다른 언어에서는 둘 다 동일합니다 (예 : Python : map(bar.process, vec), 부작용에 대한 맵은 권장되지 않으며 맵보다 목록 이해 / 생성기 표현식이 권장 됨).

5
그리고도있다 BOOST_FOREACH...
벤자민 Bannier

효과적인 STL의 항목이 당신을 설득하지 못했습니까? stackoverflow.com/questions/135129/…
직업

답변:


18

람다가 도입 된 데는 이유가 있으며, 표준위원회도 두 번째 형식이 빨 랐음을 인식하기 때문입니다. C ++ 0x 및 람다 지원을받을 때까지 첫 번째 양식을 사용하십시오.


2
동일한 추론을 사용하여 두 번째 형태를 정당화 할 수 있습니다. 표준위원회는 람다를 도입하여 훨씬 더 우수하다는 점을 인식했습니다. :-p (+1 그럼에도 불구하고)
Konrad Rudolph

두 번째는 그렇게 나쁘지 않다고 생각합니다. 그러나 더 나을 수 있습니다. 예를 들어 operator ()를 추가하지 않으면 서 의도적으로 Bar 객체를 사용하기 어렵게 만들었습니다. 이를 사용하면 루프의 호출 지점을 크게 단순화하고 코드를 깔끔하게 만듭니다.
Martin York

참고 : 두 가지 주요 불만으로 인해 람다 지원이 추가되었습니다. 1) 표준 상용구는 매개 변수를 펑터에 전달해야했습니다. 2) 호출 지점에 코드가 없습니다. 메소드를 호출 할 때 코드가 이들 중 어느 것도 만족하지 않습니다. 따라서 1) 매개 변수 전달을위한 추가 코드가 없습니다. 2) 코드가 이미 호출 지점에 없으므로 람다는 코드 유지 관리 성을 향상시키지 않습니다. 따라서 람다는 훌륭한 추가 기능입니다. 이것은 그들에게 좋은 주장이 아닙니다.
Martin York

9

항상 원하는 것을 가장 잘 나타내는 변형을 사용하십시오 . 그건

의 각 요소 x에 대해 vec수행하십시오 bar.process(x).

이제 예제를 살펴 보겠습니다.

std::for_each(vec.begin(), vec.end(),
              std::bind1st(std::mem_fun_ref(&Bar::process), bar));

우리는 for_each거기에 yippeh도 있습니다. 우리는 우리가 [begin; end)운영하고 싶은 범위를 가지고 있습니다 .

원칙적으로, 알고리즘은 훨씬 더 명시 적이 어서 수작업으로 구현 한 것보다 선호됩니다. 그러나 ... 바인더? 멤펀? 기본적으로 멤버 함수를 얻는 방법에 대한 C ++ interna? 내 임무 에는 신경 쓰지 않는다 ! 이 장황하고 소름 끼치는 구문으로 고통 받고 싶지도 않습니다.

이제 다른 가능성 :

for (std::vector<Foo>::const_iterator it = vec.begin(); it != vec.end(); ++it)
{
    bar.process(*it);
}

물론 이것은 인식 할 수있는 일반적인 패턴이지만 반복자 생성, 반복, 증분, 역 참조가 있습니다. 이것들도 내 임무를 완수하기 위해 내가 좋아하지 않는 모든 것 입니다.

틀림없이, 그것은 (적어도, 루프 waay 더 나은 최초의 솔루션보다 보이는 몸이 유연하고 매우 명시 적이다), 여전히, 정말 아니에요 것이 좋아요. 우리는 더 나은 가능성이 없다면 이것을 사용할 것이지만 아마도 우리는 ...

더 좋은 방법?

이제로 돌아갑니다 for_each. 말 그대로 말을 잘하지 않을까요 for_each 도 수행되어야하는 작업에 유연하게? 다행히도 C ++ 0x 람다이므로

for_each(v.begin(), v.end(), [&](const Foo& x) { bar.process(x); })

많은 관련 상황에 대한 추상적이고 일반적인 솔루션을 찾았 으므로이 특별한 경우에는 절대적으로 # 1 좋아하는 것이 있습니다 .

foreach(const Foo& x, vec) bar.process(x);

실제로는 그보다 훨씬 명확하지 않습니다. 고맙게도 C ++ 0x는 비슷한 문법이 내장되어 있습니다 !


2
"유사한 구문"은 for (const Foo& x : vec) bar.process(x);또는 const auto&원하는 경우 사용 중 입니다.
Jon Purdy

const auto&가능합니까? 몰랐어요-좋은 정보!
Dario

8

읽을 수 없기 때문에?

for (unsigned int i=0;i<vec.size();i++) {
{
    bar.process(vec[i]);
}

4
죄송하지만 어떤 이유로 든 귀하의 코드가 있어야한다고 생각하는 곳에 "검열"이라고 표시되어 있습니다.
Mateen Ulhaq

6
비교하기 때문에 코드는, 컴파일러 경고를 제공 intA를가 size_t위험합니다. 또한 인덱싱은 모든 컨테이너에서 작동하지 않습니다 (예 :) std::set.
fredoverflow

2
이 코드는 인덱싱 연산자가있는 컨테이너에 대해서만 작동합니다. 알고리즘이 불필요하게 연결되어 있습니다. map <>이 더 적합하다면 어떨까요? 원래 for 루프와 for_each는 영향을받지 않습니다.
JBR 윌킨슨

2
그리고 벡터의 코드를 몇 번 디자인 한 다음 맵으로 교체하기로 결정합니까? 마치 언어를 우선적으로 디자인하는 것처럼 말입니다.
Martin Beckett

2
일부 컬렉션은 인덱싱 성능이 좋지 않지만 반복자를 사용할 때는 모든 컬렉션이 잘 작동하기 때문에 인덱스별로 컬렉션을 반복하는 것은 권장하지 않습니다.
slaphappy

3

일반적으로 첫 번째 형식은 배경이 무엇이든 관계없이 for-loop가 무엇인지 아는 사람이라면 누구나 읽을 수 있습니다.

또한 일반적으로 두 번째 것은 전혀 읽을 수있는 것이 아닙니다. for_each 가하는 일을 쉽게 파악할 수는 있지만 결코 본 적이 없다면 std::bind1st(std::mem_fun_ref이해하기 어렵다고 상상할 수 있습니다.


2

실제로 C ++ 0x에서도 for_each많은 사랑을 받을지 확신 할 수 없습니다 .

for(Foo& foo: vec) { bar.process(foo); }

더 읽기 편합니다.

알고리즘 (C ++에서)에 대해 내가 싫어하는 한 가지는 반복자에 대한 추론입니다. 매우 자세한 문장을 만듭니다.


2

functor로 bar를 작성했다면 훨씬 간단합니다.

// custom functor
class Bar
{    public: void operator()(Foo& value) { /* STUFF */ }
};


std::for_each(vec.begin(), vec.end(), Bar());

여기 코드는 아주 읽기 쉽다.


2
방금 functor를 작성했다는 사실을 제외하고는 읽을 수 있습니다.
Winston Ewert

이 코드가 더 나쁘다고 생각하는 이유는 여기 를 참조 하십시오 .
sbi

1

깔끔하고 깨끗하기 때문에 후자를 선호합니다. 실제로 다른 많은 언어의 일부이지만 C ++에서는 라이브러리의 일부입니다. 실제로 중요하지 않습니다.


0

이제이 문제는 실제로 C ++에만 국한되지는 않습니다.

문제는 일부 functor를 또 다른 함수에 전달하는 것은 루프를 대체하는 것이 아닙니다 . 바로 내 마음에 오는 방문자관찰자
와 같은 기능적 프로그래밍으로 매우 자연스럽게되는 특정 패턴을 단순화 해야합니다. 1 차 함수가없는 언어에서 (Java가 가장 좋은 예일 수 있음), 이러한 접근 방식은 항상 특정 인터페이스를 구현해야합니다.

다른 언어로 많이 볼 수있는 일반적인 용도는 다음과 같습니다.

someCollection.forEach(someUnaryFunctor);

이것의 장점 someCollection은 실제로 어떻게 구현 되는지 또는 무엇을하는지 알 필요가 없다는 것 someUnaryFunctor입니다. 당신이 알아야 할 것은 그 forEach메소드가 컬렉션의 모든 요소를 ​​반복하여 주어진 함수에 전달한다는 것입니다.

개인적으로, 위치에 있다면 반복하려는 데이터 구조에 대한 모든 정보와 모든 반복 단계에서 수행하려는 작업에 대한 모든 정보를 가지려면 기능적 접근 방식이 특히 언어에서 작업을 너무 복잡하게 만듭니다. 이것은 매우 지루한 것처럼 보입니다.

또한 for 루프에없는 특정 호출이 많은 호출이 있기 때문에 기능적 접근 방식이 느리다는 점을 명심해야합니다.


1
"또한 비용이 많이 들고 for 루프에없는 많은 호출이 있기 때문에 기능적 접근 방식이 느리다는 점을 명심해야합니다." ? 이 진술은 지원되지 않습니다. 기능적 접근 방식은 반드시 속도가 느릴 필요는 없습니다. 특히 후드 아래에 최적화가있을 수 있기 때문입니다. 루프를 완전히 이해하더라도 더 추상적 인 형태로 보일 것입니다.
Andres F.

@Andres F :이게 더 깨끗해 보이나요? std::for_each(vec.begin(), vec.end(), std::bind1st(std::mem_fun_ref(&Bar::process), bar));그리고 런타임 또는 컴파일 시간이 느립니다 (운이 좋으면). 흐름 제어 구조를 함수 호출로 바꾸는 것이 왜 코드를 더 잘 만드는지 알 수 없습니다. 여기에 제시된 대안에 대해 더 읽기 쉽습니다.
back2dos

0

여기서 문제는 for_each실제로 알고리즘이 아니라는 것으로 손으로 쓴 루프를 작성하는 것은 다른 (보통 열등한 방법) 것입니다. 이 관점에서 가장 적게 사용되는 표준 알고리즘이어야하며, 아마도 for 루프를 사용할 수도 있습니다. 그러나 제목에 대한 조언은 더 구체적인 용도에 맞춘 여러 표준 알고리즘이 있기 때문에 여전히 유효합니다.

원하는 것을보다 엄격하게 수행하는 알고리즘이있는 경우 예를 들어 필기 루프보다 알고리즘을 선호해야합니다. 여기 명백한 예로 분류된다 sort거나 stable_sort또는 함께 검색 lower_bound, upper_bound또는 equal_range, 그러나 대부분은 손으로 코딩 루프를 통해 사용하는 것이 바람직있는 몇 가지 상황이있다.


0

이 작은 코드 스 니펫의 경우 둘 다 읽을 수 있습니다. 일반적으로 수동 루프 오류가 발생하기 쉽고 알고리즘 사용을 선호하지만 실제로 구체적인 상황에 달려 있습니다.

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