Std'nin avantajları :: for_each over for loop


168

std::for_eachAşırı fordöngü avantajları var mı? Bana göre, std::for_eachsadece kodun okunabilirliğini engelliyor gibi görünüyor. O zaman neden bazı kodlama standartları kullanılmasını öneriyor?


10
std::for_eachile kullanıldığında boost.lambdaveya boost.bindgenellikle okunabilirliği artırabilir

Soru kabul cevap (2018 itibaren) bir daha çağcıl yanıt için 2010 yılından vardır, buraya bakın: fluentcpp.com/2018/03/30/is-stdfor_each-obsolete
Erel Segal-HaLevi

Yanıtlar:


181

C ++ 11 (daha önce C ++ 0x olarak adlandırılıyordu) ile güzel bir şey , bu yorucu tartışmanın çözülecek olmasıdır.

Demek istediğim, bütün bir koleksiyon üzerinde yinelemek isteyen doğru akıllarında olan hiç kimse bunu kullanmayacak

for(auto it = collection.begin(); it != collection.end() ; ++it)
{
   foo(*it);
}

Veya bu

for_each(collection.begin(), collection.end(), [](Element& e)
{
   foo(e);
});

zaman aralığı tabanlı fordöngü sözdizimi mevcuttur:

for(Element& e : collection)
{
   foo(e);
}

Bu tür bir sözdizimi bir süredir Java ve C # 'da mevcuttu ve aslında gördüğüm her Java veya C # kodunda foreachklasik fordöngülerden çok daha fazla döngü var .


12
Aslında, kepçe ile bir foreach döngüsü uzun zamandır kullanılabilir durumda ve ben hala for_each ve lambda fonksiyonu ile tekrar etmek istiyorum.
Viktor Sehr

19
Tüm konteyner aralığını istemek varsayımı sorunun bir parçası değildir, bu yüzden bu sadece kısmi bir cevaptır.
Adrian McCarthy

6
Bir öğenin üzerinde döngü oluşturmanın muhtemelen yapmak istediğiniz tek şey olmadığını unutmayın, bu nedenle for_each kullanmak iyi bir fikir olabilir, böylece find / partition / copy_replace_if ve diğerleri hakkında bilgi edinebilirsiniz, bu da döngüler için çok şeydir. yapmak.
Macke

10
Range-for, gerçekten yineleyiciye ihtiyacınız olmadığı zaman güzeldir (o zaman buna ulaşmanın bir yolu yoktur).
Damon

5
Hatta kullanmamayı tercih Element & eolarak auto & e(veya auto const &e) iyi görünüyor. Bana kalırsa doğru Element const eben örtük dönüştürme istediğinizde, kaynak farklı türde bir koleksiyon olduğu zaman söylemek ve onları dönüştürmek istediğiniz (referans olmadan) Element.
Nawaz

54

İşte bazı nedenleri:

  1. Sadece buna alışık olmadığınız ve / veya gerçekten kolay olması için etrafındaki doğru araçları kullanmadığınız için okunabilirliği engelliyor gibi görünüyor. (yardımcılar için boost :: range and boost :: bind / boost :: lambda'ya bakın. Bunların çoğu C ++ 0x'e girecek ve for_each ve ilgili işlevleri daha kullanışlı hale getirecektir.)

  2. Herhangi bir yineleyici ile çalışan for_each üzerine bir algoritma yazmanıza izin verir.

  3. Aptalca yazma hataları olasılığını azaltır.

  4. Aynı zamanda, böyle STL-algoritmaları geri kalanına zihninizi açar find_if, sort, replacevb ve bu artık o kadar tuhaf bakmayacak. Bu büyük bir kazanç olabilir.

Güncelleme 1:

En önemlisi, for_eachher şey var gibi for-loop'ların ötesine geçmenize ve find / sort / partition / copy_replace_if, paralel yürütme .. veya başka bir şey gibi diğer STL-aloglarına bakmanıza yardımcı olur .

For_each'ın kardeşlerinin "geri kalanı" kullanılarak çok fazla işlem çok kısaca yazılabilir, ancak yaptığınız tek şey çeşitli dahili mantıkla bir for-loop yazmaksa, bunları nasıl kullanacağınızı asla öğrenemezsiniz ve sonunda tekerleği tekrar tekrar icat etti.

Ve (her biri için yakında kullanıma sunulacak aralık stili):

for_each(monsters, boost::mem_fn(&Monster::think));

Veya C ++ x11 lambdas ile:

for_each(monsters, [](Monster& m) { m.think(); });

IMO şunlardan daha okunabilir:

for(Monsters::iterator i = monsters.begin(); i != monsters.end(); ++i) {
    i->think();
} 

Ayrıca bu (veya lambdas ile, diğerlerine bakın):

for_each(bananas, boost::bind(&Monkey::eat, my_monkey, _1));

Şunlardan daha özlü:

for(Bananas::iterator i = bananas.begin(); i != bananas.end(); ++i) {
    my_monkey->eat(*i);
} 

Özellikle sırayla arayabileceğiniz birkaç fonksiyonunuz varsa ... ama belki bu sadece benim. ;)

Güncelleme 2 : Yineleyiciler yerine aralıklarla çalışan kendi tek katlı stl-algos sarmalayıcılarımı yazdım. boost :: range_ex, piyasaya sürüldükten sonra, bunu içerecek ve belki de C ++ 0x'de olacak mı?


+1, çeşitli işlevler veya iç içe türler: outer_class::inner_class::iteratorya da şablon argümanlarıdır: typename std::vector<T>::iterator... for yapısının kendisi kendi içinde birçok çizgi yapısına girebilir
David Rodríguez - dribeas

4
(btw: for_eachikinci örnekte yanlış (olması gerekirfor_each( bananas.begin(), bananas.end(),...
David Rodríguez - dribeas

İki yineleyici yerine aralık kullanan sarmalayıcılar yazdım. Bunlar daha sonra kullanılabilir olacaktır (aralık_ex'e bakın), ancak herkes yine de bunlara sahip olmalıdır. (Bununla ilgili güncelleme eklendi.)
Macke

1
Paralel işleme desteği hayır. Burada 1 sebep var. Heterojen paralel hesaplama için cuda / gpu kullanmak için uygulama ekleyebiliriz.
shuva

25

for_eachdaha geneldir. Herhangi bir konteyner türünü yinelemek için kullanabilirsiniz (başlangıç ​​/ bitiş yineleyicilerini ileterek). for_eachYineleme kodunu güncellemek zorunda kalmadan kullanılan bir işlevin altındaki kapları değiştirebilirsiniz . std::vectorAvantajları görmek için dünyada daha başka konteynerler ve düz eski C dizileri olduğunu düşünmelisiniz .for_each .

En büyük dezavantajı for_eachbir işlev almasıdır, bu yüzden sözdizimi tıknazdır. Bu, lambdas'ın girişiyle C ++ 11'de (eski adıyla C ++ 0x) düzeltildi:

std::vector<int> container;
...
std::for_each(container.begin(), container.end(), [](int& i){
    i+= 10;
});

Bu 3 yıl içinde size garip gelmeyecek.


3
+1. For_each, iki yineleyici yerine bir kapsayıcı / aralık aldığında reklam her zamankinden daha harika.
Macke

2
@Marcus: bu yapı için aralıklı olacak ve sözdizimi kendi içinde 'for_each' okumaz: for ( int v : int_vector ) {(bugün BOOST_FOREACH ile simüle edilebilse bile)
David Rodríguez - dribeas

@David: Menzil tabanlı işlevlerin genel eklenmesinden bahsediyorum (böylece tüm bu for_each, copy, remove_if, vb. İşlevlerle aralıkları kullanabilirsiniz),
Macke

1
Neden yazmak mümkün değildir: std::for_each(container, [](int& i){ ... });. Demek istediğim biri neden iki kez kap yazmaya zorlanıyor?
Giorgio

1
@ freitass: Kapsayıcıyı önceki yorumumda olduğu gibi bir kez yazmak, başlangıç ​​sonu yineleyicisini açıkça çağırmadan kullanmak için varsayılan olabilir. Koleksiyonlarda üst düzey işlevler sağlayan çoğu dil (Ruby, Scala, ...) başlangıç container.each { ... }ve bitiş yineleyicilerinden bahsetmeden benzer bir şey yazıyor . Ben son yineleyici her zaman belirtmek zorunda biraz gereksiz buluyorum.
Giorgio

18

Şahsen, kullanmak için yolumdan çıkmak istediğim her zaman std::for_each(özel amaçlı functors / komplike boost::lambdas yazma ), BOOST_FOREACHdaha net bulmak için C ++ 0x aralığını buluyorum :

BOOST_FOREACH(Monster* m, monsters) {
     if (m->has_plan()) 
         m->act();
}

vs

std::for_each(monsters.begin(), monsters.end(), 
  if_then(bind(&Monster::has_plan, _1), 
    bind(&Monster::act, _1)));

12

çok öznel, bazıları kullanmanın for_each kodu aynı sözleşmelerle farklı koleksiyonları işlemeye izin verdiği için kodu daha okunabilir hale getireceğini söyleyecektir . for_eachitslef bir döngü olarak uygulanır

template<class InputIterator, class Function>
  Function for_each(InputIterator first, InputIterator last, Function f)
  {
    for ( ; first!=last; ++first ) f(*first);
    return f;
  }

sizin için neyin doğru olduğunu seçmek size kalmış.


11

Algoritma işlevlerinin çoğu gibi, ilk tepki, foreach kullanmanın bir döngüden daha okunaksız olduğunu düşünmektir. Birçok alev savaşının konusu oldu.

Deyime alıştıktan sonra yararlı bulabilirsiniz. Bariz bir avantaj, kodlayıcının, halkanın iç içeriğini gerçek yineleme işlevselliğinden ayırmaya zorlamasıdır. (Tamam, bence bu bir avantaj. Diğerleri sadece gerçek bir faydası olmadan kodu parçaladığını söylüyor).

Diğer bir avantajı ise, her şeyi gördüğümde biliyorum her öğe ya işlenecektir veya bir özel durum olacağı.

A için döngü döngü sonlandırma için çeşitli seçenekler sağlar. Döngünün tam yolunu çalıştırmasına izin verebilir veya döngüden açıkça atlamak için break anahtar sözcüğünü kullanabilir veya tüm işlev orta döngüden çıkmak için return anahtar sözcüğünü kullanabilirsiniz . Aksine, foreach bu seçeneklere izin vermez ve bu da daha okunabilir olmasını sağlar. Sadece işlev adına bakabilir ve yinelemenin tam doğasını bilirsiniz.

İşte döngü için kafa karıştırıcı bir örnek :

for(std::vector<widget>::iterator i = v.begin(); i != v.end(); ++i)
{
   /////////////////////////////////////////////////////////////////////
   // Imagine a page of code here by programmers who don't refactor
   ///////////////////////////////////////////////////////////////////////
   if(widget->Cost < calculatedAmountSofar)
   {
        break;
   }
   ////////////////////////////////////////////////////////////////////////
   // And then some more code added by a stressed out juniour developer
   // *#&$*)#$&#(#)$#(*$&#(&*^$#(*$#)($*#(&$^#($*&#)$(#&*$&#*$#*)$(#*
   /////////////////////////////////////////////////////////////////////////
   for(std::vector<widgetPart>::iterator ip = widget.GetParts().begin(); ip != widget.GetParts().end(); ++ip)
   {
      if(ip->IsBroken())
      {
         return false;
      }
   }
}

1
İyi bir noktaya değiniyorsunuz, ancak motivasyon örneğiniz tamamen adil değil. Kullandığınızda std::for_each()(bu yazının zamanın) eski standardında Eğer dediğin gibi okunabilirliği teşvik eder ve döngü erken patlak yasaklayan adlandırılmış functor, kullanmak zorunda. Ama sonra eşdeğer fordöngü bir işlev çağrısından başka bir şeye sahip değildir ve bu da erken çıkmayı yasaklar. Ama bunun dışında , tüm menzil boyunca std::for_each() zorlamaların olduğunu söylerken mükemmel bir noktaya değindiğini düşünüyorum .
wilhelmtell

10

Çoğunlukla haklısın: çoğu zaman std::for_eachnet bir kayıp. Şimdiye kadar karşılaştırmak için gider for_eachiçin goto. gotomümkün olan en çok yönlü akış kontrolünü sağlar - hayal edebileceğiniz diğer tüm kontrol yapılarını uygulamak için kullanabilirsiniz. Bununla birlikte, bu çok yönlülük, gotoizole bir şekilde görmenin , bu durumda ne yapılması gerektiği hakkında neredeyse hiçbir şey söylemediği anlamına gelir . Sonuç olarak, sağ akıllarında neredeyse hiç kimse kullanmıyorgoto son çare dışında .

Standart algoritmalar arasında, for_eachneredeyse aynı yol vardır - neredeyse her şeyi uygulamak için kullanılabilir, yani görme for_eachbu durumda ne için kullanıldığı hakkında neredeyse hiçbir şey söylemez. Ne yazık ki, insanların tutumu for_each, goto1970'e ya da daha fazlasına yönelik tutumlarının nerede olduğu ile ilgilidir - birkaç kişi, sadece son çare olarak kullanılması gerektiği gerçeğini yakaladı, ancak birçoğu hala birincil algoritma olduğunu düşünüyor ve nadiren başka birini kullanırsanız. Zamanın büyük çoğunluğu, hızlı bir bakışta bile, alternatiflerden birinin büyük ölçüde üstün olduğunu ortaya çıkaracaktır.

Sadece örneğin, bir koleksiyon içeriğini kullanarak yazdırmak için insanların kaç kez kod yazdığını gördüm izini kaybettim eminim for_each. Gördüğüm yayınlara dayanarak, bu en yaygın kullanımı olabilir for_each. Sonunda şöyle bir şey ortaya çıkıyor:

class XXX { 
// ...
public:
     std::ostream &print(std::ostream &os) { return os << "my data\n"; }
};

Ve onların sonrası hangi kombinasyonu soruyor bind1st, mem_funvb onlar gibi bir şey yapmak gerekir:

std::vector<XXX> coll;

std::for_each(coll.begin(), coll.end(), XXX::print);

çalışın ve unsurlarını yazdırın coll. Eğer tam olarak orada yazdığım gibi çalışsaydı, vasat olurdu, ama işe yaramaz - ve işe yaradığınızda, ne ile ilgili bu birkaç kod parçasını bulmak zor bir arada tutan parçalar arasında devam ediyor.

Neyse ki, çok daha iyi bir yol var. XXX için normal bir akış ekleyicisi aşırı yüklemesi ekleyin:

std::ostream &operator<<(std::ostream *os, XXX const &x) { 
   return x.print(os);
}

ve kullanın std::copy:

std::copy(coll.begin(), coll.end(), std::ostream_iterator<XXX>(std::cout, "\n"));

Bu işe yarar - ve içeriğini yazdırdığını anlamak için neredeyse hiç çalışma collgerektirmez std::cout.


+1, Yine de bir hata var. İlk örnekte daha boost::mem_fn(&XXX::print)ziyade olmalıXXX::print
eksik faktor

Bu yüzden bu örneğin işe yaramayacağını söyledim ve işe yaramasını sağlamak için yardım istiyorlar (oh, ve bunun çalışması std::coutiçin argümanı olarak da bağlamanız gerekiyor ).
Jerry Coffin

1
Genellikle iyi bir cevap olmasına rağmen, soru for_each'un değeri veya diğer std algoritmalarına kıyasla değeri değil, bir for döngüsüyle karşılaştırıldığında değeri hakkındadır. Başka hiçbir std algoritmasının uygulanmadığı durumlarda bunu düşünebilirsiniz. Daha sonra for_each veya for for döngüsü kullanır mısınız? Bir düşünün ve ne bulursanız olun, cevabınız bu olmalı.
Christian Rau

@ChristianRau: Soruyu tam olarak istendiği gibi cevaplamak ile faydalı bilgiler sağlamaya çalışmak arasında her zaman ince bir çizgi vardır. Tam olarak sorduğu sorulara doğrudan cevap "Muhtemelen hayır. Kim bilir?" Olurdu, ama rahatsız etmeye değmeyecek kadar işe yaramaz olurdu. Aynı zamanda çok uzağa gitmek (örneğin, yukarıdakilerden herhangi biri yerine Haskell'i tavsiye etmek) da çok fazla yararlı olmayacaktır.
Jerry Coffin

3
@ChristianRau: "... çoğu zaman, std :: for_each net bir kayıptır" ifadesinin, std :: for_each'ın bir avantaj sağlayıp sağlamadığı sorusunu nasıl ele almadığını nasıl anlarsınız?
Jerry Coffin

8

Arıcılık için işlevsel yazmanın avantajı daha okunabilir, ne zaman for(...)ve görünmeyebilir for_each(...).

Tüm algoritmaları function.h dosyasında for-loop kullanmak yerine kullanırsanız, kod çok daha okunabilir hale gelir;

iterator longest_tree = std::max_element(forest.begin(), forest.end(), ...);
iterator first_leaf_tree = std::find_if(forest.begin(), forest.end(), ...);
std::transform(forest.begin(), forest.end(), firewood.begin(), ...);
std::for_each(forest.begin(), forest.end(), make_plywood);

bir çok daha okunabilir birden;

Forest::iterator longest_tree = it.begin();
for (Forest::const_iterator it = forest.begin(); it != forest.end(); ++it{
   if (*it > *longest_tree) {
     longest_tree = it;
   }
}

Forest::iterator leaf_tree = it.begin();
for (Forest::const_iterator it = forest.begin(); it != forest.end(); ++it{
   if (it->type() == LEAF_TREE) {
     leaf_tree  = it;
     break;
   }
}

for (Forest::const_iterator it = forest.begin(), jt = firewood.begin(); 
     it != forest.end(); 
     it++, jt++) {
          *jt = boost::transformtowood(*it);
    }

for (Forest::const_iterator it = forest.begin(); it != forest.end(); ++it{
    std::makeplywood(*it);
}

Ve bu kadar güzel olduğunu düşünüyorum, for-loop'ları bir satır fonksiyonuna genelleştirin =)


6

Kolay: for_eachHer dizi öğesini işleyecek bir işleve sahip olduğunuzda kullanışlıdır, bu nedenle bir lambda yazmak zorunda kalmazsınız. Şüphesiz, bu

for_each(a.begin(), a.end(), a_item_handler);

daha iyi

for(auto& item: a) {
    a_item_handler(a);
}

Ayrıca, aralıklı fordöngü yalnızca tüm kaplar üzerinde baştan sona yinelenir for_each, daha esnektir.


4

for_eachDöngü işlemi ile açık anlamlara kullanıcı kodu yineleyicileri (bir döngü nasıl uygulandığını detay) gizlemek ve tanımlamak için demek: her eleman sadece bir kere tekrarlanır edilecektir.

Mevcut standartta okunabilirlik sorunu, bir kod bloğu yerine son argüman olarak bir functor gerektirmesidir, bu nedenle çoğu durumda bunun için belirli functor türü yazmanız gerekir. Functor nesneleri yerinde tanımlanamadığından (işlev içinde tanımlanan yerel sınıflar şablon bağımsız değişkenleri olarak kullanılamaz) ve döngünün uygulamasının gerçek döngüden taşınması gerektiğinden, daha az okunabilir koda dönüşür.

struct myfunctor {
   void operator()( int arg1 ) { code }
};
void apply( std::vector<int> const & v ) {
   // code
   std::for_each( v.begin(), v.end(), myfunctor() );
   // more code
}

Her nesne üzerinde bir spesifik işlemi gerçekleştirmek istiyorsanız, kendinizin kullanabileceği Not std::mem_fnveya boost::bind( std::bindsonraki standardında) veya boost::lambda(sonraki standartta lambda'lar) daha basit hale getirmek için:

void function( int value );
void apply( std::vector<X> const & v ) {
   // code
   std::for_each( v.begin(), v.end(), boost::bind( function, _1 ) );
   // code
}

Eğer yerine çağıracak fonksiyon / yöntem varsa el haddelenmiş sürümü daha az okunabilir ve daha kompakt değildir. Uygulama, for_eachdöngünün diğer uygulamalarını sağlayabilir (paralel işlem düşünün).

Yaklaşan standart, bazı eksiklikleri farklı şekillerde ele alır, yerel olarak tanımlanan sınıfların şablonlara argüman olarak izin verir:

void apply( std::vector<int> const & v ) {
   // code
   struct myfunctor {
      void operator()( int ) { code }
   };
   std::for_each( v.begin(), v.end(), myfunctor() );
   // code
}

Kodun yerinin iyileştirilmesi: göz attığınızda kodun orada ne yaptığını görürsünüz. Aslında, functor'ı tanımlamak için sınıf sözdizimini kullanmanıza bile gerek yoktur, ancak burada bir lambda kullanın:

void apply( std::vector<int> const & v ) {
   // code
   std::for_each( v.begin(), v.end(), 
      []( int ) { // code } );
   // code
}

Durum for_eachiçin daha doğal hale getirecek belirli bir yapı olsa bile :

void apply( std::vector<int> const & v ) {
   // code
   for ( int i : v ) {
      // code
   }
   // code
}

Yapıyı for_eachelle haddelenmiş döngülerle karıştırma eğilimindeyim . Sadece mevcut bir fonksiyon veya yöntem için bir çağrı ne gerek ( for_each( v.begin(), v.end(), boost::bind( &Type::update, _1 ) )) Ben for_eachkod plaka kazanı yineleyici şeyler bir sürü alır yapı için gitmek . Daha karmaşık bir şeye ihtiyacım olduğunda ve gerçek kullanımın sadece birkaç satırının üstünde bir işlev uygulayamıyorsam, kendi döngümü yuvarlıyorum (işlemi yerinde tutar). Kodun kritik olmayan bölümlerinde BOOST_FOREACH ile gidebilirim (bir iş arkadaşı beni içine aldı)


3

Okunabilirlik ve performansın yanı sıra, yaygın şekilde göz ardı edilen bir husus da tutarlılıktır. Yineleyiciler üzerinde for (veya while) döngüsünü uygulamanın birçok yolu vardır:

for (C::iterator iter = c.begin(); iter != c.end(); iter++) {
    do_something(*iter);
}

için:

C::iterator iter = c.begin();
C::iterator end = c.end();
while (iter != end) {
    do_something(*iter);
    ++iter;
}

değişken verimlilik seviyeleri ve hata potansiyeli arasında birçok örnek.

Ancak for_each komutunun kullanılması, döngüyü soyutlayarak tutarlılığı zorunlu kılar:

for_each(c.begin(), c.end(), do_something);

Şimdi endişelenmeniz gereken tek şey, döngü gövdesini Boost veya C ++ 0x özelliklerini kullanarak işlev, işlev veya lambda olarak mı uyguluyorsunuz? Şahsen, bunun için bir rastgele / while döngüsü nasıl uygulanacağından veya okunacağından endişe ediyorum.


3

Sevmezdim std::for_each ve lambda olmadan tamamen yanlış yapıldığını düşündüm. Ancak bir süre önce fikrimi değiştirdim ve şimdi gerçekten seviyorum. Bence okunabilirliği bile geliştiriyor ve kodunuzu bir TDD yöntemiyle test etmeyi kolaylaştırıyor.

std::for_eachAlgoritma olarak okunabilir aralığındaki tüm unsurları ile do şey , olabilir okunabilirliği artırmak. Gerçekleştirmek istediğiniz eylemin 20 satır uzunluğunda olduğunu ve eylemin gerçekleştirildiği işlevin de yaklaşık 20 satır uzunluğunda olduğunu varsayalım. Bu, geleneksel bir döngü ile 40 satır uzunluğunda ve sadece yaklaşık 20 ile bir işlevi yapar std::for_each, böylece anlaşılması daha kolay olur.

Functor'larının std::for_eachdaha genel olma olasılığı daha yüksektir ve bu nedenle tekrar kullanılabilir, örneğin:

struct DeleteElement
{
    template <typename T>
    void operator()(const T *ptr)
    {
        delete ptr;
    }
};

Ve kodda sadece bir astar var std::for_each(v.begin(), v.end(), DeleteElement()) açık bir döngüden biraz daha iyi bir IMO .

Bu işlevlerin tümü, uzun bir fonksiyonun ortasında bir döngü için açık olandan ziyade birim testlere girmek için daha kolaydır ve bu tek başına zaten benim için büyük bir kazançtır.

std::for_each menzil ile ilgili bir hata yapma olasılığınız düşük olduğu için genellikle daha güvenilirdir.

Ve son olarak, derleyici std::for_each, döngü için el yapımı belirli türlere göre biraz daha iyi kod üretebilir , çünkü (for_each) derleyici için her zaman aynı görünür ve derleyici yazarları tüm bilgilerini koyabilirler. Yapabilmek.

Aynısı find_if, transformvb. Gibi diğer std algoritmaları için de geçerlidir .


2

forher öğeyi veya her üçte birini yineleyebilen döngü içindir vb for_each. her öğeyi yinelemek içindir. Adından açıkça anlaşılıyor. Bu yüzden kodunuzda ne yapmak istediğinizi daha açık.


4
her biri 3 ile ilerleyen bir yineleyici geçerseniz değil ++. Alışılmadık belki, ama aynı şeyi yapan bir for-loop da öyle.
Ocak'ta Potatoswatter

Bu durumda belki transformbirisini karıştırmamak için kullanmak daha iyi olacaktır .
Kirill V. Lyadvinsky

2

STL'nin diğer algoritmalarını sık sık kullanıyorsanız, aşağıdakilerin birkaç avantajı vardır for_each:

  1. Kısmen bir for döngüsünden daha basit ve daha az hataya eğilimli olacaktır, çünkü kısmen bu arayüzle işlevlere alışık olacaksınız ve kısmen de birçok durumda biraz daha özlü olduğu için.
  2. Döngü tabanlı bir aralık daha basit olsa da, daha az esnektir (Adrian McCarthy tarafından belirtildiği gibi, tüm bir kap üzerinde yinelenir).
  3. Geleneksel bir for döngüsünden farklı olarak, for_eachherhangi bir giriş yineleyicisi için çalışacak kodu yazmaya zorlar. Bu şekilde kısıtlanmak aslında iyi bir şey olabilir çünkü:

    1. Aslında kodu daha sonra farklı bir kapsayıcıda çalışacak şekilde uyarlamanız gerekebilir.
    2. Başlangıçta, size bir şeyler öğretebilir ve / veya daha iyisi için alışkanlıklarınızı değiştirebilir.
    3. Her zaman mükemmel eşdeğer döngüler için yazsanız bile, aynı kodu değiştiren diğer kişiler bunu kullanmanız istenmeden bunu yapamayabilir for_each.
  4. Kullanılması for_eachbazen aynı şeyi yapmak için daha spesifik bir STL fonksiyonunu kullanabilirsiniz o daha belirgin hale getirir. (Jerry Coffin'in örneğinde olduğu gibi; en for_eachiyi seçenek bu olmak zorunda değildir , ancak bir for döngüsü tek alternatif değildir.)


2

C ++ 11 ve iki basit şablonla,

        for ( auto x: range(v1+4,v1+6) ) {
                x*=2;
                cout<< x <<' ';
        }

yerine for_eachveya bir döngü olarak. Neden kısalık ve güvenliğe dayanacağını seçin, orada olmayan bir ifadede hata şansı yoktur.

Benim için, for_each döngü gövdesi zaten bir functor olduğunda aynı gerekçelerle her zaman daha iyiydi ve elde edebileceğim herhangi bir avantajdan yararlanacağım.

Üç ifadeyi hala kullanıyorsunuz for, ancak şimdi bir tanesini gördüğünüzde orada anlayacağınız bir şey olduğunu biliyorsunuz, kaynak plakası değil. Ben nefret klişe. Onun varlığına kızdım. Gerçek kod değil, okuyarak öğrenecek bir şey yok, kontrol edilmesi gereken bir şey daha var. Zihinsel çaba, onu kontrol etmede paslanmanın ne kadar kolay olduğu ile ölçülebilir.

Şablonlar

template<typename iter>
struct range_ { 
                iter begin() {return __beg;}    iter end(){return __end;}
            range_(iter const&beg,iter const&end) : __beg(beg),__end(end) {}
            iter __beg, __end;
};

template<typename iter>
range_<iter> range(iter const &begin, iter const &end)
    { return range_<iter>(begin,end); }

1

Çoğunlukla tüm koleksiyonu yinelemeniz gerekir . Bu nedenle, sadece 2 parametre alarak kendi for_each () değişkeninizi yazmanızı öneririm. Bu, Terry Mahaffey'in örneğini şöyle yeniden yazmanıza izin verecektir :

for_each(container, [](int& i) {
    i += 10;
});

Bu gerçekten bir for döngüsünden daha okunabilir olduğunu düşünüyorum. Ancak, bu C ++ 0x derleyici uzantıları gerektirir.


1

For_each'un okunabilirlik açısından kötü olduğunu düşünüyorum. Konsept iyi bir şey ama c ++ en azından benim için okunabilirliği zorlaştırıyor. c ++ 0x lamda ifadeleri yardımcı olacaktır. Lamdalar fikrini çok seviyorum. Ancak ilk bakışta sözdiziminin çok çirkin olduğunu düşünüyorum ve buna alışacağımdan% 100 emin değilim. Belki 5 yıl içinde buna alıştım ve ikinci bir düşünce vermeyeceğim, ama belki de yapmayacağım. Zaman gösterecek :)

Kullanmayı tercih ederim

vector<thing>::iterator istart = container.begin();
vector<thing>::iterator iend = container.end();
for(vector<thing>::iterator i = istart; i != iend; ++i) {
  // Do stuff
}

Başlangıç ​​ve bitiş yineleyicileri için adlandırılmış değişkenler kullanarak döngü temizleyici okumak ve açıklık için açık bir bulmak için for döngüsü dağınıklığı azaltır.

Tabii ki durumlar değişir, genellikle en iyi bulduğum şey budur.


0

Yineleyiciyi döngü üzerinden her yinelemede gerçekleştirilen bir işleve çağrı yapabilirsiniz.

Buraya bakın: http://www.cplusplus.com/reference/algorithm/for_each/


2
Yalnızca bağlantı gönderileri iyi yanıt vermez ve yine de, bu bağlantıda çağrılabilir bir yineleyiciye benzeyen bir şey gösterir? Eminim ki bu kavram mantıklı değil. Belki de sadece ne for_eachişe yaradığını özetlersiniz , bu durumda avantajları hakkındaki soruya cevap vermez.
underscore_d


0

for_eachFork-Join modelini uygulamamıza izin verin . Bunun dışında akıcı arayüzü destekler .

çatal birleştirme deseni

gpu::for_eachBirden fazla çalışanda lambda görevini çağırarak heterojen paralel hesaplama için cuda / gpu kullanmak için uygulama ekleyebiliriz .

gpu::for_each(users.begin(),users.end(),update_summary);
// all summary is complete now
// go access the user-summary here.

Ve gpu::for_eachişçilerin bir sonraki ifadeleri çalıştırmadan önce tüm lambda görevleri üzerinde çalışmasını bekleyebilir.

akıcı arayüzü

İnsan tarafından okunabilen kodu kısa ve öz bir şekilde yazmamızı sağlar.

accounts::erase(std::remove_if(accounts.begin(),accounts.end(),used_this_year));
std::for_each(accounts.begin(),accounts.end(),mark_dormant);
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.