Berikut adalah contoh yang disederhanakan. Pada dasarnya, ia memeriksa string dari daftar string. Jika cek lolos, itu akan menghapus string itu ( filterStringOut(i);
), dan tidak perlu lagi melanjutkan pemeriksaan lainnya. Demikian continue
ke string selanjutnya.
void ParsingTools::filterStrings(QStringList &sl)
{
/* Filter string list */
QString s;
for (int i=0; i<sl.length(); i++) {
s = sl.at(i);
// Improper length, remove
if (s.length() != m_Length) {
filterStringOut(i);
continue; // Once removed, can move on to the next string
}
// Lacks a substring, remove
for (int j=0; j<m_Include.length(); j++) {
if (!s.contains(m_Include.at(j))) {
filterStringOut(i);
/* break; and continue; */
}
}
// Contains a substring, remove
for (int j=0; j<m_Exclude.length(); j++) {
if (s.contains(m_Exclude.at(j))) {
filterStringOut(i);
/* break; and continue; */
}
}
}
}
Bagaimana seharusnya seseorang melanjutkan loop luar dari dalam loop bersarang?
Tebakan terbaik saya adalah menggunakan goto
dan menempatkan label di ujung lingkaran luar. Itu mendorong saya untuk mengajukan pertanyaan ini, mengingat bagaimana hal itu goto
bisa tabu .
Dalam obrolan c ++ IRC, disarankan agar saya menempatkan for
loop dalam fungsi bool, yang mengembalikan true jika tanda centang dilewati. jadi
if ( containsExclude(s)) continue;
if (!containsInclude(s)) continue;
atau bahwa saya cukup membuat boolean lokal, atur menjadi true break
, periksa bool dan lanjutkan jika benar.
Mengingat bahwa saya menggunakan ini dalam parser, saya sebenarnya perlu memprioritaskan kinerja dalam contoh ini. Apakah ini situasi di mana goto
masih berguna, atau ini merupakan kasus di mana saya perlu merestrukturisasi kode saya?
goto
, meskipun reputasinya buruk. Jangan takut nama - konsep ketakutan.Jawaban:
Jangan bersarang: konversikan ke fungsi saja. Dan minta fungsi-fungsi itu kembali
true
jika mereka melakukan tindakan mereka dan langkah-langkah selanjutnya dapat dilewati;false
jika tidak. Dengan cara itu Anda benar-benar menghindari seluruh masalah bagaimana keluar dari satu level, melanjutkan di dalam level lain, dll ketika Anda hanya menghubungkan panggilan dengan||
(ini mengasumsikan C ++ berhenti memproses ekspresi padatrue
; Saya pikir itu).Jadi kode Anda mungkin berakhir seperti berikut ini (saya belum menulis C ++ selama bertahun-tahun, sehingga kemungkinan mengandung kesalahan sintaks, tetapi seharusnya memberi Anda ide umum):
sumber
ParsingTools::filterStrings
) memanggilfilterStringOut(i)
fungsi tersebut, seperti yang ditunjukkan dalam jawabanDari sudut pandang lebih banyak burung, saya akan refactor kode sehingga terlihat seperti ini ... (dalam kode semu, sudah terlalu lama saya menyentuh C ++)
Ini adalah IMHO cleaner karena dengan jelas memisahkan apa yang merupakan string yang tepat, dan apa yang Anda lakukan saat tidak.
Anda bahkan dapat melangkah lebih jauh dan menggunakan metode filter bawaan seperti
myProperStrings = allMyStrings.filter(isProperString)
sumber
Saya sangat suka bagaimana @agnagnies dimulai . Singkat dan langsung ke inti nya. Penggunaan abstraksi tingkat tinggi yang baik. Saya hanya mengubah tanda tangan dan menghindari hal-hal negatif yang tidak perlu.
Namun, saya suka bagaimana @DavidArno memecah tes persyaratan sebagai fungsi individual. Tentu semuanya menjadi lebih panjang tetapi setiap fungsi luar biasa kecil. Nama mereka menghindari perlunya komentar untuk menjelaskan siapa mereka. Saya hanya tidak suka mereka mengambil tanggung jawab ekstra untuk menelepon
filterStringOut()
.Omong-omong, ya C ++ akan menghentikan evaluasi
||
rantaitrue
selama Anda belum membebani||
operator. Ini disebut evaluasi hubung singkat . Tapi ini adalah optimasi mikro sepele yang Anda bebas abaikan ketika Anda membaca kode selama fungsinya bebas efek samping (seperti yang di bawah).Yang berikut harus membuat definisi string tolak menjadi jelas tanpa menyeret Anda melalui detail yang tidak perlu:
Lega dari kebutuhan untuk memanggil
filterStringOut()
fungsi tes persyaratan menjadi lebih pendek dan nama mereka jauh lebih sederhana. Saya juga menaruh semua yang mereka andalkan dalam daftar parameter mereka untuk membuatnya lebih mudah untuk memahaminya tanpa melihat ke dalam.Saya menambahkan
requiredSubstring
danforbiddenSubstring
untuk manusia. Apakah mereka akan memperlambat Anda? Tes dan temukan. Lebih mudah untuk membuat kode yang dapat dibaca benar-benar cepat daripada membuat kode yang dioptimalkan secara prematur dapat dibaca atau benar-benar cepat.Jika fungsi ternyata memperlambat Anda lihatlah ke fungsi sebaris sebelum Anda membuat manusia kode yang tidak dapat dibaca. Sekali lagi, jangan menganggap ini akan memberi Anda kecepatan. Uji.
Saya pikir Anda akan menemukan ini lebih mudah dibaca daripada bersarang untuk loop. Mereka, dikombinasikan dengan
if
's, mulai memberi Anda panah anti-pola nyata . Saya pikir pelajarannya di sini adalah bahwa fungsi kecil adalah hal yang baik.sumber
! isProperString
daripadaisImproperString
disengaja. Saya cenderung menghindari negasi dalam nama fungsi. Bayangkan Anda perlu memeriksa apakah itu benar-benar string yang tepat nanti, Anda akan membutuhkan!isImproperString
IMHO yang lebih rentan terhadap kebingungan karena negasi ganda.Cukup gunakan lambda untuk predikat, dan kemudian gunakan kekuatan algoritma standar dan hubungan arus pendek. Tidak perlu untuk aliran kontrol berbelit-belit atau eksotis:
sumber
Ada juga opsi untuk membuat konten dari loop luar (yang Anda ingin melanjutkan) lambda , dan cukup gunakan
return
.Sangat mudah jika Anda tahu lambdas; Anda pada dasarnya memulai interior lingkaran Anda dengan
[&]{
dan mengakhirinya dengan}()
; di dalam Anda dapat menggunakanreturn;
kapan saja untuk meninggalkannya:sumber
continue
danbreak
harus digantireturn
. Kode Anda tampaknya meninggalkan tempat pertama (yang menggunakancontinue
) tidak berubah, tetapi itu harus diubah juga, karena kode di dalam lambda dancontinue
pernyataan tidak dapat menemukan ruang lingkup yang merupakan lingkaran.Saya pikir @dganelies memiliki ide yang tepat sebagai titik awal, tapi saya pikir saya akan mempertimbangkan untuk melangkah lebih jauh: menulis fungsi generik yang dapat menjalankan pola yang sama untuk (hampir) wadah, kriteria, dan tindakan apa pun:
Anda
filterStrings
kemudian hanya akan menentukan kriteria, dan lulus tindakan yang sesuai:Tentu saja, ada cara lain untuk mendekati masalah mendasar itu juga. Misalnya, menggunakan perpustakaan standar, Anda sepertinya menginginkan sesuatu dengan urutan umum yang sama
std::remove_if
.sumber
Beberapa jawaban menyarankan refactor utama kode. Ini mungkin bukan cara yang buruk untuk pergi, tetapi saya ingin memberikan jawaban yang lebih sesuai dengan pertanyaan itu sendiri.
Aturan # 1: Profil sebelum dioptimalkan
Selalu profil hasil sebelum mencoba optimasi. Anda mungkin menghabiskan banyak waktu jika tidak melakukannya.
Yang telah dibilang...
Seperti itu, saya secara pribadi telah menguji kode semacam ini di MSVC. Boolean adalah cara untuk pergi. Beri nama boolean sesuatu yang semantik bermakna seperti
containsString
.Pada MSVC (2008), dalam mode rilis (pengaturan pengoptimal khas), kompiler mengoptimalkan loop yang sama ke persis set yang sama dari opcodes seperti
goto
versi. Cukup cerdas untuk melihat bahwa nilai boolean secara langsung terkait dengan kontrol aliran, dan menyingkirkan semuanya. Saya belum menguji gcc, tapi saya kira itu bisa melakukan optimasi serupa.Ini memiliki keunggulan dibandingkan
goto
tidak hanya menimbulkan kekhawatiran oleh kaum puritan yang dianggapgoto
berbahaya, tanpa mengorbankan nilai kinerja instruksi tunggal.sumber