Elle yazılmış döngüler için algoritmaları mı tercih ediyorsunuz?


10

Aşağıdakilerden hangisini daha okunabilir buluyorsunuz? Elle yazılmış döngü:

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

Veya algoritma çağırma:

#include <algorithm>
#include <functional>

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

Ben std::for_eachgerçekten buna değer olup olmadığını merak ediyorum , böyle basit bir örnek verilen çok fazla kod gerektirir.

Bu konudaki düşünceleriniz neler?


2
C ++ için, cevap oldukça açıktır. Ancak diğer dillerde, her ikisi de eşittir (örn. Python: map(bar.process, vec)yan etkiler için harita önerilmez ve harita üzerinde liste kavrayışları / jeneratör ifadeleri önerilir).

5
Ve sonra da var BOOST_FOREACH...
Benjamin Bannier

Peki, etkili STL'deki eşya sizi ikna etmedi mi? stackoverflow.com/questions/135129/…
Job

Yanıtlar:


18

Lambdaların getirilmesinin bir nedeni var ve bunun nedeni Standart Komite'nin bile ikinci formun berbat olduğunu kabul etmesi. C ++ 0x ve lambda desteği alana kadar ilk formu kullanın.


2
Aynı mantık ikinci formu haklı çıkarmak için de kullanılabilir: Standart Komite, daha da iyi hale getirmek için lambdaları tanıtmanın çok daha üstün olduğunu kabul etti. :-p (yine de +1)
Konrad Rudolph

Sanırım ikincisi o kadar kötü değil. Ama daha iyi olabilir. Örneğin, operatör () eklemeden Bar nesnesini kullanmayı daha zor hale getirdiniz. Bunu kullanarak döngüdeki çağrı noktasını büyük ölçüde basitleştirir ve kodu düzenli hale getirir.
Martin York

Not: İki ana şikayet nedeniyle lambda desteği eklendi. 1) Parametreleri functorlara iletmek için gerekli standart kazan plakası. 2) Kodun çağrı noktasında olmaması. Yalnızca bir yöntem çağırdığınız için kodunuz bunlardan hiçbirini karşılamıyor. Yani 1) parametreleri iletmek için ekstra kod yok 2) Kod zaten çağrı noktasında değil, bu yüzden lambda kodun sürdürülebilirliğini artırmayacak. Yani evet lambda harika bir ektir. Bu onlar için iyi bir argüman değil.
Martin York

9

Her zaman ne yapmak istediğinizi en iyi açıklayan değişkeni kullanın . Yani

Her öğe için xde vecyapın bar.process(x).

Şimdi örnekleri inceleyelim:

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

Biz var for_eachorada da - yippeh . [begin; end)Üzerinde çalışmak istediğimiz ürün yelpazemiz var .

Prensip olarak, algoritma çok daha açıktı ve bu nedenle elle yazılmış herhangi bir uygulamaya göre tercih edilebilirdi. Ama sonra ... Bağlayıcılar? Memfun? Temelde bir C ++ interna nasıl üye fonksiyonu ele almak için? Görevim için onları umursamıyorum ! Ben de bu ayrıntılı, ürkütücü sözdiziminden muzdarip olmak istemiyorum.

Şimdi diğer olasılık:

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

Kabul edersek, bu tanımak için yaygın bir örüntüdür, ama ... yineleyiciler oluşturmak, döngü oluşturmak, arttırma, kayıttan kaldırma. Bunlar da görevimi yerine getirmek için umursamadığım şeyler .

Kuşkusuz, ilk çözümden daha iyi gözüküyor (en azından, döngü gövdesi esnek ve oldukça açık), ancak yine de, gerçekten o kadar büyük değil . Daha iyi bir imkanımız yoksa bunu kullanacağız, ama belki de ...

En iyi yol?

Şimdi geri dönelim for_each. Yapılması gereken operasyonda tam anlamıyla söylemek for_each ve esnek olmak harika olmaz mıydı ? Neyse ki, C ++ 0x lambdas'dan beri

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

Birçok ilgili duruma soyut, genel bir çözüm bulduğumuza göre, bu özel durumda, mutlak bir # 1 favori olduğunu belirtmek gerekir :

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

Gerçekten bundan daha net olamaz. Neyse ki, C ++ 0x benzer benzer bir sözdizimi yerleşiktir !


2
"Benzer sözdizimi" for (const Foo& x : vec) bar.process(x);veya const auto&isterseniz kullanın.
Jon Purdy

const auto&mümkün? Bunu bilmiyordum - harika bilgi!
Dario

8

Çünkü bu okunamıyor mu?

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

4
Üzgünüz, ama nedense kodunuzun olması gerektiğine inandığım "sansür" diyor.
Mateen Ulhaq

6
Bir karşılaştırma çünkü kod, bir derleyici uyarı verir intbir ile size_ttehlikelidir. Ayrıca, endeksleme her kapsayıcıyla çalışmaz std::set.
fredoverflow

2
Bu kod yalnızca endeksleme operatörüne sahip konteynırlar için geçerlidir, bunlardan azı vardır. Algoritmayı gereksiz yere bağlar - ya bir harita <> daha uygunsa? Döngü ve for_each için orijinal etkilenmez.
JBRWilkinson

2
Ve bir vektör için kaç kez kod tasarlayıp ardından bir harita için değiştirmeye karar veriyorsunuz? Sanki bunun için tasarım yapmak dil önceliğiydi.
Martin Beckett

2
Bazı koleksiyonların dizin oluşturmada performansı zayıf olduğu için koleksiyonların dizine göre yinelenmesi önerilmez, ancak tüm koleksiyonlar yineleyici kullanırken güzel davranır.
slaphappy

3

genel olarak, ilk form, wat arka planı ne olursa olsun, for-döngüsünün ne olduğunu bilen herkes tarafından okunabilir.

ayrıca genel olarak, ikincisi o kadar okunabilir değil: for_each'un ne yaptığını bulmak yeterince kolay, ama daha önce hiç görmediyseniz std::bind1st(std::mem_fun_refanlamak zor.


2

Aslında, C ++ 0x ile bile, for_eachçok sevgi alacak emin değilim .

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

Çok daha okunabilir.

Algoritmalar hakkında sevmediğim tek şey (C ++ 'da) yineleyiciler üzerinde muhakeme yapmaktır, çok ayrıntılı ifadeler yapar.


2

Bir functor olarak bar yazmış olsaydınız, çok daha basit olurdu:

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


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

Burada kod oldukça okunabilir.


2
Sadece bir functor yazdığınız gerçeği dışında oldukça okunabilir.
Winston Ewert

Neden bu kod daha kötü olduğunu düşünüyorum için buraya bakın .
sbi

1

İkincisini tercih ederim çünkü temiz ve temiz. Aslında diğer birçok dilin bir parçası ama C ++ 'da kütüphanenin bir parçası. Gerçekten önemli değil.


0

Şimdi bu sorun aslında C ++ özgü değildir, iddia ediyorum.

Mesele şu ki, bazı işlevleri başka bir işleve geçirmek döngülerin yerini almak değildir . Hemen aklıma gelen ziyaretçi ve gözlemci
gibi işlevsel programlama ile çok doğal hale gelen bazı kalıpları basitleştirmesi gerekiyor . Birinci dereceden işlevleri olmayan dillerde (Java muhtemelen en iyi örnektir), bu tür yaklaşımlar her zaman belirli bir arayüzün uygulanmasını gerektirir, bu da oldukça verbous ve gereksizdir.

Diğer dillerde çok gördüğüm yaygın bir kullanım:

someCollection.forEach(someUnaryFunctor);

Bunun avantajı someCollection, aslında nasıl uygulandığını veya ne yaptığını someUnaryFunctorbilmemeniz gerektiğidir. Bilmeniz gereken tek şey, forEachyönteminin koleksiyonun tüm öğelerini yineleyerek verilen işleve geçireceğidir.

Şahsen, eğer pozisyondaysanız, tekrarlamak istediğiniz veri yapısı ve her tekrarlama adımında ne yapmak istediğiniz hakkında tüm bilgilere sahip olmak için, o zaman fonksiyonel bir yaklaşım, özellikle bir dilde, şeyleri aşırı karmaşıklaştırır, bu görünüşte oldukça sıkıcı.

Ayrıca, fonksiyonel yaklaşımın daha yavaş olduğunu unutmayın, çünkü belirli bir maliyetle gelen ve for döngüsünde bulunmadığınız çok fazla çağrınız var.


1
"Ayrıca, fonksiyonel yaklaşımın daha yavaş olduğunu aklınızdan çıkarmamalısınız, çünkü belirli bir maliyetle gelen, bir for döngüsünde bulunmadığınız çok fazla çağrınız var." ? Bu ifade desteklenmiyor. İşlevsel yaklaşım, özellikle kaputun altında optimizasyonlar olabileceğinden, daha yavaş değildir. Ve döngünüzü tamamen anlasanız bile, muhtemelen daha soyut formunda daha temiz görünecektir.
Andres F.7

@Andres F: Yani bu daha temiz görünüyor? std::for_each(vec.begin(), vec.end(), std::bind1st(std::mem_fun_ref(&Bar::process), bar));Ve ya çalışma zamanında daha yavaş ya da derleme zamanı (eğer şanslıysanız). Akış kontrol yapılarını işlev çağrılarıyla değiştirmenin neden kodu daha iyi hale getirdiğini anlamıyorum. Burada sunulan herhangi bir alternatif hakkında daha okunabilir.
back2dos

0

Burada sorun for_eachgerçekten bir algoritma değil, sadece elle yazılmış bir döngü yazmak için sadece farklı (ve genellikle daha düşük bir yol) olduğunu düşünüyorum. bu açıdan en az kullanılan standart algoritma olmalıdır ve evet muhtemelen for döngüsü de kullanabilirsiniz. ancak başlıktaki öneri, daha spesifik kullanımlar için uyarlanmış birkaç standart algoritma olduğu için hala geçerlidir.

İstediğinizi daha sıkı yapan bir algoritma olduğunda, evet, elle yazılmış döngüler yerine algoritmaları tercih etmelisiniz. Burada bariz örnekler ile sıralama vardır sortya stable_sortya ile arama lower_bound, upper_boundya da equal_range, ancak bunların çoğu onlar bir el kodlu döngü üzerinde kullanılması tercih olduğu kimi durum var.


0

Bu küçük kod parçacığı için, her ikisi de bana eşit olarak okunabilir. Genel olarak, manuel döngüler hataya eğilimli buluyorum ve algoritmalar kullanmayı tercih ediyorum, ancak gerçekten somut bir duruma bağlı.

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.