Rozwiązanie algorytmu:
std::generate(numbers.begin(), numbers.end(), rand);
Rozwiązanie for-loop oparte na zakresie:
for (int& x : numbers) x = rand();
Dlaczego miałbym chcieć używać bardziej szczegółowych std::generate
pętli for opartych na zakresie w C ++ 11?
begin()
iend()
?range
funkcję w swoim zestawie narzędzi. (tj.for(auto& x : range(first, last))
)boost::generate(numbers, rand); // ♪
Odpowiedzi:
Pierwsza wersja
std::generate(numbers.begin(), numbers.end(), rand);
mówi nam, że chcesz wygenerować sekwencję wartości.
W drugiej wersji czytelnik będzie musiał sam to rozgryźć.
Oszczędność na pisaniu jest zwykle nieoptymalna, ponieważ najczęściej tracona jest na czasie czytania. Większość kodu jest czytana znacznie częściej niż jest wpisywana.
źródło
numbers
dwa razy bez powodu. Stąd:boost::range::generate(numbers, rand);
. Nie ma powodu, dla którego nie możesz mieć jednocześnie krótszego i bardziej czytelnego kodu w dobrze zbudowanej bibliotece.std::generate(number.begin(), numbers.begin()+3, rand)
, prawda? Więcnumber
wydaje mi się, że określenie dwa razy może być czasami przydatne.std::generate()
, możesz zamiast tego zrobićstd::generate(slice(number.begin(), 3), rand)
lub nawet lepiej, używając hipotetycznej składni krojenia zakresu, takiej jak ta,std::generate(number[0:3], rand)
która usuwa powtórzenie,number
jednocześnie umożliwiając elastyczną specyfikację części zakresu. Odwrotna sytuacja, zaczynając od trzech argumentów,std::generate()
jest bardziej uciążliwa.To, czy pętla for jest oparta na zakresie, czy nie, w ogóle nie robi różnicy, tylko upraszcza kod wewnątrz nawiasów. Algorytmy są wyraźniejsze, ponieważ pokazują zamiar .
źródło
Osobiście moje pierwsze czytanie:
std::generate(numbers.begin(), numbers.end(), rand);
to „przypisujemy wszystko w zakresie. Zakres to
numbers
. Przypisane wartości są losowe”.Moja pierwsza lektura:
for (int& x : numbers) x = rand();
to „robimy coś dla wszystkiego w zakresie. Zakres wynosi
numbers
. To, co robimy, to przypisywanie losowej wartości”.Te są cholernie podobne, ale nie identyczne. Jednym z możliwych powodów, dla których mógłbym chcieć sprowokować pierwsze czytanie, jest to, że uważam, że najważniejszym faktem dotyczącym tego kodu jest to, że przypisuje on zakres. Więc jest twoje "dlaczego miałbym chcieć ...". Używam,
generate
ponieważ w C ++std::generate
oznacza „przypisanie zakresu”. Tak przy okazjistd::copy
, różnica między nimi polega na tym, z czego przypisujesz.Istnieją jednak czynniki mylące. Pętle for oparte na zakresie mają z natury bardziej bezpośredni sposób wyrażania, że jest to zakres
numbers
, niż robią to algorytmy oparte na iteratorach. Dlatego ludzie pracują na bibliotekach algorytmów opartych na zakresach:boost::range::generate(numbers, rand);
wygląda lepiej niżstd::generate
wersja.W przeciwieństwie do tego,
int&
w twoim zakresie pętli for jest zmarszczka. A co jeśli typ wartości zakresu nie jestint
, to robimy tutaj coś irytująco subtelnego, co zależy od tego, czy można go zamienić naint&
, podczas gdygenerate
kod zależy tylko od powrotu zrand
przypisania do elementu. Nawet jeśli typ wartości toint
, nadal mogę przestać myśleć o tym, czy tak jest, czy nie. Stądauto
, co odkłada myślenie o typach, dopóki nie zobaczę, co zostanie przypisane -auto &x
mówię „weź odwołanie do elementu zakresu, dowolnego typu, który może mieć”. Powrót w C ++ 03, algorytmy (bo są szablony funkcyjne) były sposób ukryć dokładnych typów, teraz są sposób.Myślę, że zawsze było tak, że najprostsze algorytmy miały tylko marginalną korzyść w porównaniu z równoważnymi pętlami. Oparte na zakresie pętle for poprawiają pętle (głównie poprzez usunięcie większości kotłów, chociaż jest ich trochę więcej). Tak więc marginesy są coraz ciaśniejsze i być może zmienisz zdanie w niektórych szczególnych przypadkach. Ale nadal istnieje różnica stylu.
źródło
operator int&()
? :)int&
zSomeClass&
a teraz trzeba się martwić o operatorów konwersji i konstruktorów pojedynczy parametr nie oznakowanychexplicit
.operator int&()
ioperator int const &() const
, ale z drugiej strony może działać przez przeciążenieoperator int() const
ioperator=(int)
.Moim zdaniem, efektywny element STL 43: „Preferuj wywołania algorytmów od ręcznie pisanych pętli”. to nadal dobra rada.
Zwykle piszę funkcje opakowujące, aby pozbyć się
begin()
/end()
hell. Jeśli to zrobisz, Twój przykład będzie wyglądał następująco:Uważam, że pokonuje zakres oparty na pętli, zarówno pod względem komunikowania intencji, jak i czytelności.
Powiedziawszy to, muszę przyznać, że w C ++ 98 niektóre wywołania algorytmu STL dawały kod, którego nie można było wypowiedzieć, a następująca po tym fraza „Preferuj wywołania algorytmu zamiast ręcznie pisanych pętli” nie wydawała się dobrym pomysłem. Na szczęście lambdy to zmieniły.
Rozważmy następujący przykład z Herba Suttera: Lambdas, Lambdas Everywhere .
Zadanie: znajdź pierwszy element w v, czyli
> x
i< y
.Bez lambd:
auto i = find_if( v.begin(), v.end(), bind( logical_and<bool>(), bind(greater<int>(), _1, x), bind(less<int>(), _1, y) ) );
Z lambdą
auto i=find_if( v.begin(), v.end(), [=](int i) { return i > x && i < y; } );
źródło
W moim zdaniem, instrukcja pętli, chociaż może zmniejszyć szczegółowość, brakuje readabitly:
for (int& x : numbers) x = rand();
Nie użyłbym tej pętli do zainicjowania 1 zakresu określonego liczbami , bo kiedy na to patrzę, wydaje mi się, że iteruje po zakresie liczb, ale w rzeczywistości nie (w istocie), tj. Zamiast czytając z zakresu, pisze do zakresu.
Intencja jest znacznie wyraźniejsza, gdy używasz
std::generate
.1. inicjalizacja w tym kontekście oznacza nadanie znaczącej wartości elementom kontenera.
źródło
std::generate
, co można założyć w przypadku programisty C ++ (jeśli nie są zaznajomieni, sprawdzą to, ten sam wynik).&
znaku?). Zaletą algorytmów jest to, że pokazują zamiar, podczas gdy w przypadku pętli musisz to wywnioskować. Jeśli w implementacji pętli występuje błąd, nie jest jasne, czy jest to błąd, czy był zamierzony.std::generate
zwykłego spojrzenia można powiedzieć, że coś jest generowane przez tę funkcję; na co to coś odpowiada trzeci argument funkcji. Myślę, że to jest znacznie lepsze.Są rzeczy, których nie można zrobić (po prostu) z pętlami opartymi na zakresie, ponieważ algorytmy przyjmują iteratory jako dane wejściowe. Na przykład z
std::generate
:Wypełnij kontener do
limit
(wykluczone,limit
to poprawny iterator włączonynumbers
) zmiennymi z jednej dystrybucji, a resztę zmiennymi z innej dystrybucji.std::generate(numbers.begin(), limit, rand1); std::generate(limit, numbers.end(), rand2);
Algorytmy oparte na iteratorze zapewniają lepszą kontrolę nad zakresem, na którym pracujesz.
źródło
W konkretnym przypadku
std::generate
zgadzam się z poprzednimi odpowiedziami dotyczącymi czytelności / intencji. std :: generowanie wydaje mi się bardziej przejrzystą wersją. Ale przyznaję, że to w pewnym sensie kwestia gustu.To powiedziawszy, mam jeszcze jeden powód, aby nie wyrzucać algorytmu std :: - istnieją pewne algorytmy, które są wyspecjalizowane dla niektórych typów danych.
Najprostszym przykładem byłoby
std::fill
. Wersja ogólna jest implementowana jako pętla for w podanym zakresie i ta wersja będzie używana podczas tworzenia wystąpienia szablonu. Ale nie zawsze. Np. Jeśli podasz mu zakres, który jeststd::vector<int>
- często będzie faktycznie wywoływałmemset
pod maską, dając znacznie szybszy i lepszy kod.Więc próbuję zagrać tutaj kartę efektywności.
Twoja odręczna pętla może być tak szybka jak wersja std :: algorytm, ale prawie nie może być szybsza. Co więcej, algorytm std :: może być wyspecjalizowany dla określonych kontenerów i typów i odbywa się to w ramach czystego interfejsu STL.
źródło
Moja odpowiedź brzmiałaby może i nie. Jeśli mówimy o C ++ 11, to może (bardziej jak nie). Na przykład
std::for_each
jest naprawdę denerwujące w użyciu nawet z lambdami:std::for_each(c.begin(), c.end(), [&](ExactTypeOfContainedValue& x) { // do stuff with x });
Ale używanie oparte na zakresie dla jest o wiele lepsze:
for (auto& x : c) { // do stuff with x }
Z drugiej strony, jeśli mówimy o C ++ 1y, to argumentowałbym, że nie, algorytmy nie zostaną przestarzałe przez zakres oparty na. W komisji normalizacyjnej C ++ istnieje grupa analityczna, która pracuje nad propozycją dodania zakresów do C ++, a także trwają prace nad polimorficznymi lambdami. Zakresy wyeliminowałyby potrzebę używania pary iteratorów, a polimorficzna lambda pozwoliłaby ci nie określać dokładnego typu argumentu lambda. Oznacza to, że
std::for_each
można go użyć w ten sposób (nie traktuj tego jako twardego faktu, tak po prostu wyglądają sny dzisiaj):std::for_each(c.range(), [](x) { // do stuff with x });
źródło
[]
z lambdą określasz przechwytywanie zerowe? Oznacza to, że w porównaniu z samym pisaniem treści pętli wyizolowałeś fragment kodu z kontekstu wyszukiwania zmiennych, w którym występuje on leksykalnie. Izolacja jest zwykle pomocna dla czytelnika, a mniej do myślenia podczas czytania.for_each
jest nadal bezcelowe, nawet jeśli jest używane z lambdą. foreach + przechwytywanie lambda jest obecnie rozwlekłym sposobem pisania pętli for opartej na zakresie i staje się nieco mniej szczegółowym sposobem, ale nadal bardziej niż pętla.for_each
Oczywiście nie uważam, że powinieneś się bronić , ale jeszcze przed zobaczeniem twojej odpowiedzi pomyślałem, że gdyby pytający chciał pokonać algorytmy, mógłbyfor_each
wybrać jako najdelikatniejszy z możliwych celów ;-)for_each
, ale ma jedną niewielką przewagę nad opartą na zasięgu - możesz uczynić go równoległym łatwiej, po prostu poprzedzając go przedrostkiem parallel_, aby go zamienićparallel_for_each
(jeśli używasz PPL i zakładając, że jest to bezpieczne wątkowo) . :-Dstd::algorithm
implementacja s jest ukryta za ich interfejsem i może być dowolnie złożona (lub dowolnie zoptymalizowana).Należy zauważyć, że algorytm wyraża to, co się robi, a nie jak.
Pętla oparta na zakresie obejmuje sposób wykonywania czynności: zacznij od pierwszego, zastosuj i przejdź do następnego elementu aż do końca. Nawet prosty algorytm mógłby zrobić coś inaczej (przynajmniej niektóre przeciążenia dla określonych kontenerów, nawet nie myśląc o okropnym wektorze), a przynajmniej sposób, w jaki to się robi, nie jest biznesem pisarza.
Dla mnie to duża różnica, hermetyzuj tyle, ile możesz, a to uzasadnia zdanie, kiedy możesz, użyj algorytmów.
źródło
Oparta na zakresie pętla for jest właśnie tym. Dopóki oczywiście nie zmieni się standard.
Algorytm to funkcja. Funkcja, która stawia pewne wymagania swoim parametrom. Wymagania są sformułowane w standardzie, aby umożliwić na przykład implementację, która wykorzystuje wszystkie dostępne wątki wykonawcze i przyspiesza automatycznie.
źródło