Jawaban yang menarik: Meskipun saya setuju dengan semuanya (sejauh ini), ada kemungkinan konotasi untuk pertanyaan ini yang sampai sekarang benar-benar diabaikan.
Jika contoh sederhana di atas diperluas dengan alokasi sumber daya, dan kemudian pengecekan kesalahan dengan potensi pembebasan sumber daya, gambar mungkin berubah.
Pertimbangkan pendekatan naif yang mungkin diambil pemula:
int func(..some parameters...) {
res_a a = allocate_resource_a();
if (!a) {
return 1;
}
res_b b = allocate_resource_b();
if (!b) {
free_resource_a(a);
return 2;
}
res_c c = allocate_resource_c();
if (!c) {
free_resource_b(b);
free_resource_a(a);
return 3;
}
do_work();
free_resource_c(c);
free_resource_b(b);
free_resource_a(a);
return 0;
}
Di atas akan mewakili versi ekstrim dari gaya kembali sebelum waktunya. Perhatikan bagaimana kode menjadi sangat berulang dan tidak dapat dipelihara seiring waktu ketika kompleksitasnya bertambah. Saat ini orang mungkin menggunakan penanganan pengecualian untuk menangkap ini.
int func(..some parameters...) {
res_a a;
res_b b;
res_c c;
try {
a = allocate_resource_a(); # throws ExceptionResA
b = allocate_resource_b(); # throws ExceptionResB
c = allocate_resource_c(); # throws ExceptionResC
do_work();
}
catch (ExceptionBase e) {
# Could use type of e here to distinguish and
# use different catch phrases here
# class ExceptionBase must be base class of ExceptionResA/B/C
if (c) free_resource_c(c);
if (b) free_resource_b(b);
if (a) free_resource_a(a);
throw e
}
return 0;
}
Philip menyarankan, setelah melihat contoh goto di bawah ini, untuk menggunakan sakelar / casing tanpa pemutus di dalam blok penangkap di atas. Seseorang dapat beralih (typeof (e)) dan kemudian jatuh melalui free_resourcex()
panggilan tetapi ini tidak sepele dan perlu pertimbangan desain . Dan ingat bahwa sakelar / casing tanpa jeda persis seperti goto dengan label rantai daisy di bawah ini ...
Seperti yang ditunjukkan Mark B, di C ++, gaya yang baik untuk mengikuti Akuisisi Sumber Daya adalah prinsip Inisialisasi , RAII singkatnya . Inti dari konsep ini adalah menggunakan instansiasi objek untuk memperoleh sumber daya. Sumber daya kemudian secara otomatis dibebaskan segera setelah objek keluar dari ruang lingkup dan penghancurnya dipanggil. Untuk sumber daya yang saling bergantung, perhatian khusus harus dilakukan untuk memastikan urutan deallokasi yang benar dan untuk merancang jenis objek sedemikian rupa sehingga data yang diperlukan tersedia untuk semua destruktor.
Atau di hari-hari pra-pengecualian mungkin bisa:
int func(..some parameters...) {
res_a a = allocate_resource_a();
res_b b = allocate_resource_b();
res_c c = allocate_resource_c();
if (a && b && c) {
do_work();
}
if (c) free_resource_c(c);
if (b) free_resource_b(b);
if (a) free_resource_a(a);
return 0;
}
Tetapi contoh yang terlalu disederhanakan ini memiliki beberapa kelemahan: Dapat digunakan hanya jika sumber daya yang dialokasikan tidak bergantung satu sama lain (misalnya tidak dapat digunakan untuk mengalokasikan memori, kemudian membuka filehandle, lalu membaca data dari pegangan ke dalam memori ), dan tidak memberikan kode kesalahan individual yang dapat dibedakan sebagai nilai pengembalian.
Untuk menjaga kode tetap cepat (!), Kompak, dan mudah dibaca dan diperluas, Linus Torvalds menerapkan gaya berbeda untuk kode kernel yang berhubungan dengan sumber daya, bahkan menggunakan goto yang terkenal dengan cara yang benar-benar masuk akal :
int func(..some parameters...) {
res_a a;
res_b b;
res_c c;
a = allocate_resource_a() || goto error_a;
b = allocate_resource_b() || goto error_b;
c = allocate_resource_c() || goto error_c;
do_work();
error_c:
free_resource_c(c);
error_b:
free_resource_b(b);
error_a:
free_resource_a(a);
return 0;
}
Inti dari diskusi di milis kernel adalah bahwa sebagian besar fitur bahasa yang "lebih disukai" daripada pernyataan goto adalah goto implisit, seperti if / else besar, penangan pengecualian, pernyataan loop / break / lanjutkan, dll. . Dan goto pada contoh di atas dianggap baik, karena mereka hanya melompat dalam jarak kecil, memiliki label yang jelas, dan membebaskan kode dari kekacauan lain untuk melacak kondisi kesalahan. Pertanyaan ini juga telah dibahas di sini di stackoverflow .
Namun apa yang hilang dalam contoh terakhir adalah cara yang bagus untuk mengembalikan kode kesalahan. Saya berpikir untuk menambahkan result_code++
setelah setiap free_resource_x()
panggilan, dan mengembalikan kode itu, tetapi ini mengimbangi beberapa peningkatan kecepatan dari gaya pengkodean di atas. Dan sulit untuk mengembalikan 0 jika berhasil. Mungkin saya hanya tidak imajinatif ;-)
Jadi, ya, menurut saya ada perbedaan besar dalam pertanyaan tentang pengkodean pengembalian dini atau tidak. Tetapi saya juga berpikir itu hanya terlihat dalam kode yang lebih rumit yang lebih sulit atau tidak mungkin untuk direstrukturisasi dan dioptimalkan untuk kompiler. Yang biasanya terjadi setelah alokasi sumber daya mulai berlaku.