Lebih suka algoritma ke loop tulisan tangan?

10

Manakah dari berikut ini yang menurut Anda lebih mudah dibaca? Lingkaran tulisan tangan:

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

Atau doa algoritma:

#include <algorithm>
#include <functional>

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

Saya bertanya-tanya apakah std::for_eachbenar-benar layak, mengingat contoh sederhana seperti itu sudah membutuhkan begitu banyak kode.

Apa pendapat Anda tentang hal ini?

fredoverflow
sumber
2
Untuk C ++, jawabannya cukup jelas. Tetapi dalam bahasa lain, keduanya hampir sama (misalnya Python:, map(bar.process, vec)meskipun peta untuk efek samping tidak disarankan dan daftar pemahaman / ekspresi generator direkomendasikan pada peta).
5
Dan kemudian ada juga BOOST_FOREACH...
Benjamin Bannier
Jadi, item dalam STL yang efektif tidak meyakinkan Anda? stackoverflow.com/questions/135129/…
Job

Jawaban:

18

Ada alasan mengapa lambda diperkenalkan, dan itu karena bahkan Komite Standar mengakui bahwa bentuk kedua menyebalkan. Gunakan formulir pertama, sampai Anda mendapatkan dukungan C ++ 0x dan lambda.

DeadMG
sumber
2
Alasan yang sama dapat digunakan untuk membenarkan bentuk kedua: Komite Standar mengakui bahwa itu jauh lebih unggul sehingga mereka memperkenalkan lambda untuk membuatnya lebih baik. :-p (+1 tetap)
Konrad Rudolph
Saya tidak berpikir untuk yang kedua adalah yang buruk. Tapi itu bisa lebih baik. Misalnya Anda sengaja membuat objek Bar lebih sulit digunakan dengan tidak menambahkan operator (). Dengan menggunakan ini sangat menyederhanakan titik panggilan dalam loop dan membuat kodenya rapi.
Martin York
Catatan: dukungan lambda ditambahkan karena dua keluhan utama. 1) Pelat standar diperlukan untuk melewatkan parameter ke functors. 2) Tidak memiliki kode di titik panggilan. Kode Anda tidak memenuhi keduanya karena Anda hanya memanggil metode. Jadi 1) tidak ada kode tambahan untuk melewati parameter 2) Kode sudah tidak ada di titik panggilan sehingga lambda tidak akan meningkatkan rawatan kode. Jadi ya lambda adalah tambahan yang bagus. Ini bukan argumen yang bagus untuk mereka.
Martin York
9

Selalu gunakan varian yang paling menggambarkan apa yang ingin Anda lakukan. Itu adalah

Untuk setiap elemen xdalam vec, lakukan bar.process(x).

Sekarang, mari kita periksa contoh-contohnya:

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

Kami punya di for_eachsana juga - Yippeh . Kami memiliki [begin; end)rentang yang ingin kami operasikan.

Pada prinsipnya, algoritma itu jauh lebih eksplisit dan dengan demikian lebih disukai daripada implementasi tulisan tangan. Tapi kemudian ... Binder? Memfun? Pada dasarnya C ++ interna tentang cara mendapatkan fungsi anggota? Untuk tugas saya, saya tidak peduli tentang mereka! Saya juga tidak ingin menderita sintaksis verbose yang menyeramkan ini.

Sekarang kemungkinan lainnya:

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

Memang, ini adalah pola umum untuk dikenali, tapi ... membuat iterator, looping, incrementing, dereferencing. Ini juga semua hal yang tidak saya pedulikan untuk menyelesaikan tugas saya.

Diakui, terlihat waay lebih baik dari solusi pertama (setidaknya, loop tubuh yang fleksibel dan cukup eksplisit), tapi masih, itu tidak benar-benar yang besar. Kami akan menggunakan ini jika kami tidak memiliki kemungkinan yang lebih baik, tapi mungkin kami ...

Cara yang lebih baik?

Sekarang kembali ke for_each. Bukankah bagus untuk mengatakan for_each dan fleksibel dalam operasi yang harus dilakukan juga? Untungnya, sejak C ++ 0x lambdas, kami

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

Sekarang kami telah menemukan solusi abstrak dan generik untuk banyak situasi terkait, perlu dicatat bahwa dalam kasus khusus ini, ada favorit # 1 mutlak :

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

Benar-benar tidak bisa lebih jelas dari itu. Untungnya, C ++ 0x mendapatkan ini sintaks mirip built-in !

Dario
sumber
2
Mengatakan "sintaksis yang mirip" sedang for (const Foo& x : vec) bar.process(x);, atau menggunakan const auto&jika Anda suka.
Jon Purdy
const auto&adalah mungkin? Tidak tahu itu - info hebat!
Dario
8

Karena ini sangat tidak dapat dibaca?

for (unsigned int i=0;i<vec.size();i++) {
{
    bar.process(vec[i]);
}
Martin Beckett
sumber
4
Maaf, tetapi untuk beberapa alasan dikatakan "disensor" di mana saya percaya kode Anda seharusnya.
Mateen Ulhaq
6
Kode Anda memberikan peringatan kompiler, karena membandingkan intdengan dengan size_tberbahaya. Juga, pengindeksan tidak berfungsi pada setiap wadah, misalnya std::set.
fredoverflow
2
Kode ini hanya akan berfungsi untuk kontainer dengan operator pengindeksan, yang jumlahnya sedikit. Ini mengikat algoritme secara tidak wajar - bagaimana jika peta <> lebih cocok? Dokumen asli untuk loop dan for_each tidak akan terpengaruh.
JBRWilkinson
2
Dan berapa kali Anda mendesain kode untuk vektor dan kemudian memutuskan untuk menukarnya dengan peta? Seolah-olah merancang untuk itu adalah prioritas bahasa.
Martin Beckett
2
Iterasi atas koleksi berdasarkan indeks tidak disarankan karena beberapa koleksi memiliki kinerja yang buruk pada pengindeksan, sedangkan semua koleksi berperilaku baik saat menggunakan iterator.
slaphappy
3

secara umum, bentuk pertama dapat dibaca oleh hampir semua orang yang tahu apa itu for-loop, tidak peduli apa latar belakang yang mereka miliki.

juga secara umum, yang kedua tidak bisa dibaca sama sekali: cukup mudah mencari tahu apa yang for_each lakukan, tetapi jika Anda belum pernah melihat std::bind1st(std::mem_fun_refsaya bisa membayangkan itu sulit untuk dimengerti.

stijn
sumber
2

Sebenarnya, bahkan dengan C ++ 0x, saya tidak yakin for_eachakan mendapatkan banyak cinta.

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

Jauh lebih mudah dibaca.

Satu hal yang saya tidak suka tentang algoritma (dalam C + +) adalah mereka beralasan di iterator, membuat pernyataan yang sangat verbose.

Matthieu M.
sumber
2

Jika Anda telah menulis bilah sebagai functor maka itu akan jauh lebih sederhana:

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


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

Di sini kodenya cukup mudah dibaca.

Martin York
sumber
2
Ini cukup mudah dibaca selain dari fakta bahwa Anda baru saja menulis sebuah functor.
Winston Ewert
Lihat di sini mengapa saya pikir kode ini lebih buruk.
sbi
1

Saya lebih suka yang terakhir karena rapi dan bersih. Ini sebenarnya bagian dari banyak bahasa lain tetapi di C ++, itu bagian dari perpustakaan. Itu tidak terlalu penting.

sarat
sumber
0

Sekarang masalah ini sebenarnya tidak spesifik untuk C ++, saya akan mengklaim.

Masalahnya, melewatkan beberapa functor ke fungsi lain tidak dimaksudkan untuk mengganti loop .
Seharusnya menyederhanakan pola tertentu, yang menjadi sangat alami dengan pemrograman fungsional, seperti pengunjung dan pengamat , hanya untuk menyebutkan dua, yang langsung muncul di pikiran saya.
Dalam bahasa yang tidak memiliki fungsi urutan pertama (Java mungkin adalah contoh terbaik), pendekatan seperti itu selalu membutuhkan penerapan beberapa antarmuka yang diberikan, yang cukup verba dan berlebihan.

Penggunaan umum yang sering saya lihat dalam bahasa lain adalah:

someCollection.forEach(someUnaryFunctor);

Keuntungan dari ini adalah, bahwa Anda tidak perlu tahu bagaimana someCollectionsebenarnya diimplementasikan atau someUnaryFunctortidak. Yang perlu Anda ketahui adalah, bahwa forEachmetodenya akan mengulangi semua elemen koleksi, meneruskannya ke fungsi yang diberikan.

Secara pribadi, jika Anda berada dalam posisi, untuk memiliki semua informasi tentang struktur data yang ingin Anda iterate dan semua informasi tentang apa yang ingin Anda lakukan dalam setiap langkah iterasi, maka pendekatan fungsional adalah hal-hal yang rumit, terutama dalam bahasa, di mana ini tampaknya cukup membosankan.

Juga, Anda harus ingat, bahwa pendekatan fungsional lebih lambat, karena Anda memiliki banyak panggilan, yang datang dengan biaya tertentu, yang tidak Anda miliki dalam perulangan for.

back2dos
sumber
1
"Juga, Anda harus ingat, bahwa pendekatan fungsional lebih lambat, karena Anda memiliki banyak panggilan, yang datang dengan biaya tertentu, yang tidak Anda miliki dalam perulangan for." ? Pernyataan ini tidak didukung. Pendekatan fungsional belum tentu lebih lambat, terutama karena mungkin ada optimasi di bawah tenda. Dan bahkan jika Anda benar-benar memahami loop Anda, itu mungkin akan terlihat lebih bersih dalam bentuk yang lebih abstrak.
Andres F.
@Andres F: Jadi ini terlihat lebih bersih? std::for_each(vec.begin(), vec.end(), std::bind1st(std::mem_fun_ref(&Bar::process), bar));Dan itu lebih lambat saat runtime atau waktu kompilasi (jika Anda beruntung). Saya tidak melihat mengapa mengganti struktur kontrol aliran dengan pemanggilan fungsi membuat kode lebih baik. Tentang segala alternatif yang disajikan di sini lebih mudah dibaca.
back2dos
0

Saya pikir masalahnya di sini adalah bahwa for_eachitu bukan algoritma, itu hanya cara yang berbeda (dan biasanya lebih rendah) untuk menulis loop tulisan tangan. dari perspektif ini seharusnya algoritma standar yang paling sedikit digunakan, dan ya Anda mungkin juga menggunakan for loop. namun saran dalam judul masih berlaku karena ada beberapa algoritma standar yang disesuaikan dengan penggunaan yang lebih spesifik.

Di mana ada algoritma yang lebih ketat melakukan apa yang Anda inginkan maka ya Anda harus memilih algoritma melalui loop tulisan tangan. contoh yang jelas di sini adalah pengurutan dengan sortatau stable_sortatau pencarian dengan lower_bound, upper_boundatau equal_range, tetapi kebanyakan dari mereka memiliki beberapa situasi di mana mereka lebih disukai untuk digunakan daripada loop kode tangan.

jk.
sumber
0

Untuk cuplikan kode kecil ini, keduanya sama-sama dapat dibaca oleh saya. Secara umum, saya menemukan loop manual rawan kesalahan dan lebih suka menggunakan algoritma, tetapi itu benar-benar tergantung pada situasi yang konkret.

Nemanja Trifunovic
sumber