Coba-coba atau jika untuk penanganan kesalahan di C ++

30

Apakah pengecualian digunakan secara luas dalam desain mesin game atau lebih baik menggunakan pernyataan murni jika? Misalnya dengan pengecualian:

try {
    m_fpsTextId = m_statistics->createText( "FPS: 0", 16, 20, 20, 1.0f, 1.0f, 1.0f );
    m_cpuTextId = m_statistics->createText( "CPU: 0%", 16, 20, 40, 1.0f, 1.0f, 1.0f );
    m_frameTimeTextId = m_statistics->createText( "Frame time: 0", 20, 20, 60, 1.0f, 1.0f, 1.0f );
    m_mouseCoordTextId = m_statistics->createText( "Mouse: (0, 0)", 20, 20, 80, 1.0f, 1.0f, 1.0f );
    m_cameraPosTextId = m_statistics->createText( "Camera pos: (0, 0, 0)", 40, 20, 100, 1.0f, 1.0f, 1.0f );
    m_cameraRotTextId = m_statistics->createText( "Camera rot: (0, 0, 0)", 40, 20, 120, 1.0f, 1.0f, 1.0f );
} catch ... {
    // some info about error
}

dan dengan seandainya:

m_fpsTextId = m_statistics->createText( "FPS: 0", 16, 20, 20, 1.0f, 1.0f, 1.0f );
if( m_fpsTextId == -1 ) {
    // show error
}
m_cpuTextId = m_statistics->createText( "CPU: 0%", 16, 20, 40, 1.0f, 1.0f, 1.0f );
if( m_cpuTextId == -1 ) {
    // show error
}
m_frameTimeTextId = m_statistics->createText( "Frame time: 0", 20, 20, 60, 1.0f, 1.0f, 1.0f );
if( m_frameTimeTextId == -1 ) {
    // show error
}
m_mouseCoordTextId = m_statistics->createText( "Mouse: (0, 0)", 20, 20, 80, 1.0f, 1.0f, 1.0f );
if( m_mouseCoordTextId == -1 ) {
    // show error
}
m_cameraPosTextId = m_statistics->createText( "Camera pos: (0, 0, 0)", 40, 20, 100, 1.0f, 1.0f, 1.0f );
if( m_cameraPosTextId == -1 ) {
    // show error
}
m_cameraRotTextId = m_statistics->createText( "Camera rot: (0, 0, 0)", 40, 20, 120, 1.0f, 1.0f, 1.0f );
if( m_cameraRotTextId == -1 ) {
    // show error
}

Saya mendengar bahwa pengecualian sedikit lebih lambat daripada jika dan saya tidak boleh mencampur pengecualian dengan metode jika-memeriksa. Namun dengan pengecualian saya memiliki lebih banyak kode yang dapat dibaca daripada dengan banyak ifs setelah setiap metode initialize () atau yang serupa, meskipun kadang-kadang terlalu berat untuk hanya satu metode menurut saya. Apakah mereka baik untuk pengembangan game atau lebih baik untuk tetap dengan jika sederhana?

tobi
sumber
Banyak orang mengklaim bahwa Boost buruk untuk pengembangan game, tetapi saya sarankan Anda melihat ["Boost.optional"] ( boost.org/doc/libs/1_52_0/libs/optional/doc/html/index.html ) itu akan membuat contoh ifs Anda sedikit lebih baik
ManicQin
Ada pembicaraan yang bagus tentang penanganan kesalahan oleh Andrei Alexandrescu, saya menggunakan pendekatan yang mirip dengannya tanpa kecuali.
Thelvyn

Jawaban:

48

Jawaban singkat: baca Penanganan Kesalahan Sensible 1 , Penanganan Kesalahan Sensible 2 , dan Penanganan Kesalahan Sensible 3 oleh Niklas Frykholm. Sebenarnya, baca semua artikel di blog itu, selagi Anda melakukannya. Tidak akan mengatakan saya setuju dengan segalanya, tetapi sebagian besar adalah emas.

Jangan gunakan pengecualian. Ada banyak alasan. Saya akan daftar yang utama.

Mereka memang bisa lebih lambat, meskipun itu diminimalkan sedikit pada kompiler baru. Beberapa kompiler mendukung "pengecualian overhead nol" untuk jalur kode yang tidak benar-benar memicu pengecualian (meskipun itu sedikit bohong, karena masih ada data tambahan yang memerlukan penanganan pengecualian, kembung ukuran executable / dll Anda). Hasil akhirnya adalah ya, menggunakan pengecualian lebih lambat, dan dalam setiap jalur kode kritis kinerja , Anda harus benar-benar menghindarinya. Bergantung pada kompiler Anda, mengaktifkannya sama sekali dapat menambah overhead. Mereka selalu selalu mengasapi ukuran kode, agak signifikan dalam banyak kasus, yang dapat sangat mempengaruhi kinerja perangkat keras saat ini.

Pengecualian membuat kode jauh lebih rapuh. Ada grafik terkenal (yang sayangnya saya tidak dapat menemukan sekarang) yang pada dasarnya hanya menunjukkan pada grafik kesulitan menulis kode pengecualian-aman vs pengecualian-kode tidak aman, dan yang pertama adalah bar yang jauh lebih besar. Hanya ada banyak gotcha kecil dengan pengecualian, dan banyak banyak cara untuk menulis kode yang terlihat pengecualian aman tetapi sebenarnya tidak. Bahkan seluruh komite C ++ 11 melakukan kesalahan dalam hal ini dan lupa untuk menambahkan fungsi pembantu penting untuk menggunakan std :: unique_ptr dengan benar, dan bahkan dengan fungsi-fungsi pembantu itu, dibutuhkan lebih banyak pengetikan untuk menggunakannya daripada tidak dan sebagian besar programmer tidak akan Bahkan tidak menyadari apa yang salah jika mereka tidak melakukannya.

Lebih khusus untuk industri gim, kompiler / runtime yang disediakan vendor beberapa konsol secara langsung tidak mendukung pengecualian sepenuhnya, atau bahkan tidak mendukungnya sama sekali. Jika kode Anda menggunakan pengecualian, mungkin Anda masih di hari ini dan usia harus menulis ulang bagian dari kode Anda untuk port ke platform baru. (Tidak yakin apakah itu berubah dalam 7 tahun sejak konsol tersebut dirilis; kami hanya tidak menggunakan pengecualian, bahkan menonaktifkannya di pengaturan kompiler, jadi saya tidak tahu bahwa siapa pun yang saya ajak bicara bahkan telah memeriksa baru-baru ini.)

Garis pemikiran umum cukup jelas: gunakan pengecualian untuk keadaan luar biasa . Gunakan ketika program Anda masuk ke "Saya tidak tahu apa yang harus dilakukan, mungkin semoga orang lain melakukannya, jadi saya akan melemparkan pengecualian dan melihat apa yang terjadi." Gunakan ketika tidak ada pilihan lain yang masuk akal. Gunakan saat Anda tidak peduli jika Anda secara tidak sengaja membocorkan sedikit memori atau gagal membersihkan sumber daya karena Anda mengacaukan penggunaan smart handle yang tepat. Dalam semua kasus lain, jangan gunakan itu.

Mengenai kode seperti contoh Anda, Anda memiliki beberapa cara lain untuk memperbaiki masalah. Salah satu yang lebih kuat - meskipun tidak selalu paling ideal dalam contoh sederhana Anda - adalah condong ke arah tipe kesalahan monadik. Yaitu, createText () dapat mengembalikan tipe pegangan kustom daripada bilangan bulat. Jenis pegangan ini memiliki pengakses untuk memperbarui atau mengendalikan teks. Jika gagang dimasukkan ke dalam status kesalahan (karena createText () gagal) maka panggilan lebih lanjut ke gagang hanya gagal diam-diam. Anda juga dapat meminta gagang untuk melihat apakah ada kesalahan, dan jika demikian, apa kesalahan terakhir itu. Pendekatan ini memiliki lebih banyak overhead daripada opsi lain, tetapi cukup solid. Gunakan dalam kasus ketika Anda perlu melakukan serangkaian operasi panjang dalam beberapa konteks di mana setiap operasi tunggal dapat gagal dalam produksi, tetapi di mana Anda tidak / tidak bisa / tidak akan menang '

Alternatif untuk mengimplementasikan penanganan kesalahan monadik adalah, daripada menggunakan objek pegangan kustom, membuat metode pada objek konteks dengan anggun menangani id pegangan tidak valid. Misalnya, jika createText () mengembalikan -1 ketika gagal, maka panggilan lain ke m_statistics yang mengambil salah satu dari pegangan itu harus keluar dengan anggun jika -1 dilewatkan.

Anda juga dapat menempatkan pencetakan kesalahan di dalam fungsi yang sebenarnya gagal. Dalam contoh Anda, createText () kemungkinan memiliki lebih banyak informasi tentang apa yang salah, sehingga ia dapat membuang kesalahan yang lebih bermakna ke log. Ada sedikit manfaat dalam hal ini untuk mendorong kesalahan penanganan / pencetakan ke penelepon. Lakukan itu ketika penelepon harus menyesuaikan penanganan (atau gunakan injeksi ketergantungan). Perhatikan bahwa memiliki konsol dalam gim yang dapat muncul setiap kali kesalahan dicatat adalah ide yang bagus dan juga membantu di sini.

Opsi terbaik (sudah disajikan dalam seri artikel terkait di atas) untuk panggilan yang Anda tidak harapkan gagal di lingkungan waras - seperti tindakan sederhana membuat gumpalan teks untuk sistem statistik - adalah hanya memiliki fungsi yang gagal (createText dalam contoh Anda) batalkan. Anda dapat yakin bahwa createText () tidak akan gagal dalam produksi kecuali ada sesuatu yang benar-benar dihapus (misalnya, pengguna menghapus file data font, atau karena alasan tertentu hanya memiliki 256MB memori, dll). Dalam banyak kasus ini, bahkan tidak ada hal yang baik untuk dilakukan ketika kegagalan terjadi. Kehabisan memori? Anda bahkan mungkin tidak dapat membuat alokasi yang diperlukan untuk membuat panel GUI yang bagus untuk menunjukkan kepada pengguna kesalahan OOM. Font yang hilang? Membuatnya sulit untuk menampilkan kesalahan kepada pengguna. Apapun yang salah,

Hanya menabrak sama sekali baik-baik saja selama Anda (a) mencatat kesalahan ke file log dan (b) hanya melakukannya pada kesalahan yang tidak disebabkan oleh tindakan pengguna biasa.

Saya tidak akan mengatakan hal yang sama untuk banyak aplikasi server, di mana ketersediaan sangat penting dan pemantauan anjing penjaga tidak cukup, tetapi itu sangat berbeda dari pengembangan klien game . Saya juga akan sangat menjauhkan Anda dari penggunaan C / C ++ di sana karena fasilitas penanganan pengecualian dari bahasa lain cenderung tidak menggigit seperti C ++ karena mereka dikelola lingkungan dan tidak memiliki semua masalah keamanan pengecualian yang dimiliki C ++. Masalah kinerja apa pun juga dikurangi karena server cenderung lebih fokus pada paralelisme dan throughput daripada jaminan latensi minimum seperti klien game. Bahkan server game aksi untuk penembak dan sejenisnya dapat berfungsi dengan cukup baik ketika ditulis dalam C #, misalnya, karena mereka jarang mendorong perangkat keras ke batasnya seperti yang cenderung dilakukan klien FPS.

Sean Middleditch
sumber
1
+1. Selain itu ada kemungkinan untuk menambahkan atribut ingin warn_unused_resultberfungsi di beberapa kompiler yang memungkinkan untuk menangkap kode kesalahan yang tidak ditangani.
Maciej Piechotka
Datang ke sini melihat C ++ dalam judul, dan benar-benar yakin penanganan kesalahan monadik belum akan ada di sini. Barang bagus!
Adam
bagaimana dengan tempat-tempat di mana kinerja tidak kritis dan Anda sebagian besar bertujuan untuk implementasi cepat misalnya ketika Anda menerapkan logika game?
Ali1S232
juga bagaimana dengan gagasan menggunakan penanganan pengecualian hanya untuk tujuan debugging? seperti saat Anda merilis game, Anda mengharapkan semuanya berjalan lancar tanpa masalah. jadi Anda menggunakan pengecualian untuk menemukan dan memperbaiki bug selama pengembangan dan kemudian dalam mode rilis menghapus semua pengecualian itu.
Ali1S232
1
@ Gajoo: untuk logika game, pengecualian hanya membuat logika lebih sulit untuk diikuti (karena membuat semua kode lebih sulit untuk diikuti). Bahkan dalam Pythnn dan C # pengecualian jarang terjadi untuk logika game, menurut pengalaman saya. untuk debugging, pernyataan keras biasanya jauh lebih nyaman. Hancurkan dan hentikan pada saat yang tepat ada yang tidak beres, tidak setelah melepaskan sebagian besar tumpukan dan kehilangan semua jenis informasi kontekstual karena pengecualian melemparkan semantik. Untuk logika debugging, Anda ingin membuat alat dan info / edit GUI, sehingga desainer dapat memeriksa dan mengubah.
Sean Middleditch
13

Kebijaksanaan lama Donald Knuth sendiri adalah:

"Kita harus melupakan tentang inefisiensi kecil, katakanlah sekitar 97% dari waktu: optimasi prematur adalah akar dari semua kejahatan."

Cetak ini sebagai poster besar dan gantung di mana pun Anda melakukan pemrograman serius.

Jadi, bahkan jika mencoba / menangkap sedikit lebih lambat maka saya akan menggunakannya secara default:

  • Kode harus dibaca dan dimengerti. Anda mungkin menulis kode sekali tetapi Anda akan membacanya berkali-kali untuk men -debug kode ini, untuk men-debug kode lain, untuk memahami, untuk perbaikan, ...

  • Ketepatan atas kinerja. Melakukan hal-hal dengan benar bukan hal sepele. Dalam contoh Anda ini dilakukan secara tidak benar, karena tidak satu kesalahan dapat ditampilkan tetapi lebih dari satu. Anda harus menggunakan cascaded if / then / else.

  • Lebih mudah digunakan secara konsisten: Coba / tangkap mencakup gaya di mana Anda tidak perlu memeriksa kesalahan setiap baris. Di sisi lain: Satu yang hilang jika / lain dan kode Anda dapat merusak. Tentu saja hanya dalam keadaan yang tidak dapat diproduksi kembali.

Jadi poin terakhir:

Saya mendengar bahwa pengecualiannya sedikit lebih lambat daripada seandainya

Saya mendengarnya sekitar 15 tahun yang lalu, belum pernah mendengarnya dari sumber yang dapat dipercaya baru-baru ini. Compiler mungkin telah meningkat atau apa pun.

Poin utamanya adalah: Jangan melakukan optimasi prematur. Lakukan hanya ketika Anda dapat membuktikan dengan tolok ukur bahwa kode yang ada adalah perulangan ketat dari beberapa kode yang banyak digunakan dan gaya beralih meningkatkan kinerja secara signifikan . Ini adalah kasus 3%.

AH
sumber
22
Saya akan -1 ini hingga tak terbatas. Saya tidak dapat memahami bahaya yang telah dilakukan kutipan Knuth ini terhadap otak pengembang. Mempertimbangkan apakah akan menggunakan pengecualian bukan optimasi prematur, itu adalah keputusan desain, keputusan desain penting yang memiliki konsekuensi jauh melampaui kinerja.
sam hocevar
3
@ SamHocevar: Maaf, saya tidak mengerti maksud Anda: Pertanyaannya adalah tentang kemungkinan kinerja yang dicapai saat menggunakan pengecualian. Maksud saya: Jangan dipikirkan, pukulannya tidak terlalu buruk (jika ada) dan hal-hal lain jauh lebih penting. Di sini Anda tampaknya setuju dengan menambahkan "keputusan desain" ke daftar saya. BAIK. Di sisi lain Anda mengatakan kutipan Knuth buruk yang menyiratkan bahwa pengoptimalan prematur tidak buruk. Tapi inilah yang terjadi di sini IMO: Q tidak berpikir tentang arsitektur, desain atau algoritma yang berbeda, hanya tentang pengecualian dan dampak kinerjanya.
AH
2
Saya tidak setuju dengan kejelasan dalam C ++. C ++ memiliki pengecualian yang tidak dicentang sementara beberapa kompiler menerapkan nilai pengembalian yang diperiksa. Akibatnya Anda memiliki kode yang terlihat lebih jelas tetapi mungkin memiliki kebocoran memori tersembunyi atau bahkan menempatkan kode dalam keadaan tidak terdefinisi. Kode pihak ketiga juga tidak terkecuali aman. (Situasinya berbeda di Java / C # yang telah memeriksa pengecualian, GC, ...). Pada saat keputusan desain - jika mereka tidak melewati poin API, refactoring dari / ke masing-masing style dapat dilakukan secara semi-otomatis dengan perl one-liner.
Maciej Piechotka
1
@SamHocevar: " Pertanyaannya adalah tentang apa yang disukai, bukan apa yang lebih cepat. " Baca ulang paragraf terakhir dari pertanyaan. Satu- satunya alasan dia bahkan berpikir untuk tidak menggunakan pengecualian adalah karena dia pikir itu mungkin lebih lambat. Sekarang jika Anda berpikir bahwa ada masalah lain yang tidak diperhitungkan OP, silakan mempostingnya atau mendukung mereka yang melakukannya. Namun OP sangat jelas berfokus pada kinerja pengecualian.
Nicol Bolas
3
@AH - jika Anda akan mengutip Knuth maka jangan lupa sisanya (dan IMO bagian paling penting): " Namun kita tidak boleh melewatkan peluang kita dalam 3% kritis itu. Seorang programmer yang baik tidak akan terbuai oleh kepuasan dengan alasan seperti itu, ia akan bijaksana untuk melihat dengan hati-hati kode kritis; tetapi hanya setelah kode itu diidentifikasi . " Terlalu sering orang melihat ini sebagai alasan untuk tidak mengoptimalkan sama sekali, atau untuk mencoba membenarkan kode yang lambat dalam kasus di mana kinerja bukan merupakan optimasi tetapi sebenarnya merupakan persyaratan mendasar.
Maximus Minimus
10

Sudah ada jawaban yang sangat bagus untuk pertanyaan ini, tetapi saya ingin menambahkan beberapa pemikiran tentang keterbacaan kode dan pemulihan kesalahan dalam kasus khusus Anda.

Saya yakin kode Anda sebenarnya dapat terlihat seperti ini:

m_fpsTextId = m_statistics->createText( "FPS: 0", 16, 20, 20, 1.0f, 1.0f, 1.0f );
m_cpuTextId = m_statistics->createText( "CPU: 0%", 16, 20, 40, 1.0f, 1.0f, 1.0f );
m_frameTimeTextId = m_statistics->createText( "Frame time: 0", 20, 20, 60, 1.0f, 1.0f, 1.0f );
m_mouseCoordTextId = m_statistics->createText( "Mouse: (0, 0)", 20, 20, 80, 1.0f, 1.0f, 1.0f );
m_cameraPosTextId = m_statistics->createText( "Camera pos: (0, 0, 0)", 40, 20, 100, 1.0f, 1.0f, 1.0f );
m_cameraRotTextId = m_statistics->createText( "Camera rot: (0, 0, 0)", 40, 20, 120, 1.0f, 1.0f, 1.0f );

Tidak ada pengecualian, tidak ada jika. Pelaporan kesalahan dapat dilakukan di createText. Dan createTextdapat mengembalikan ID tekstur default yang tidak mengharuskan Anda untuk memeriksa nilai kembali, sehingga sisa kode berfungsi dengan baik.

sam hocevar
sumber