Lemparkan kata kunci dalam tanda tangan fungsi

199

Apa alasan teknis mengapa dianggap praktik yang buruk untuk menggunakan throwkata kunci C ++ dalam tanda tangan fungsi?

bool some_func() throw(myExc)
{
  ...
  if (problem_occurred) 
  {
    throw myExc("problem occurred");
  }
  ...
}
Konstantin
sumber
Lihat pertanyaan terkait baru-baru ini: stackoverflow.com/questions/1037575/…
laalto
1
Apakah ada noexceptperubahan?
Aaron McDaid
12
Tidak ada yang salah dengan memiliki pendapat tentang sesuatu yang berhubungan dengan pemrograman. Kriteria penutupan ini buruk, setidaknya untuk pertanyaan ini. Ini adalah pertanyaan yang menarik dengan jawaban yang menarik.
Anders Lindén
Perlu dicatat bahwa spesifikasi pengecualian sudah tidak digunakan lagi sejak C ++ 11.
François Andrieux

Jawaban:

127

Tidak, itu tidak dianggap praktik yang baik. Sebaliknya, itu umumnya dianggap ide yang buruk.

http://www.gotw.ca/publications/mill22.htm menjelaskan lebih banyak tentang mengapa, tetapi masalahnya adalah sebagian kompiler tidak dapat menegakkan ini, sehingga harus diperiksa saat runtime, yang biasanya tidak diinginkan. Dan itu tidak didukung dengan baik dalam hal apapun. (MSVC mengabaikan spesifikasi pengecualian, kecuali throw (), yang ia interpretasikan sebagai jaminan bahwa tidak ada pengecualian yang akan dilemparkan.

jalf
sumber
25
Iya. Ada cara yang lebih baik untuk menambahkan spasi putih ke kode Anda daripada membuang (myEx).
Assaf Lavie
4
ya, orang-orang yang baru saja menemukan spesifikasi pengecualian sering menganggap bahwa mereka bekerja seperti di Jawa, di mana kompiler dapat menegakkannya. Di C ++, itu tidak akan terjadi, yang membuat mereka jauh lebih tidak berguna.
jalf
7
Tetapi bagaimana dengan tujuan dokumenter itu? Selain itu, Anda akan diberi tahu pengecualian mana yang tidak akan pernah Anda tangkap meskipun Anda mencobanya.
Anders Lindén
1
@ AndersLindén tujuan dokumenter apa? Jika Anda hanya ingin mendokumentasikan perilaku kode Anda, cukup beri komentar di atasnya. Tidak yakin apa yang Anda maksud dengan bagian kedua. Pengecualian Anda tidak akan pernah menangkap bahkan jika Anda mencoba?
Jalf
4
Saya pikir kode sangat kuat ketika datang untuk mendokumentasikan kode Anda. (komentar bisa berbohong). Efek "dokumenter" yang saya maksudkan adalah bahwa Anda akan mengetahui dengan pasti pengecualian mana yang dapat Anda tangkap dan yang lainnya tidak.
Anders Lindén
57

Jalf sudah terhubung dengannya, tetapi GOTW mengatakannya dengan cukup baik mengapa spesifikasi pengecualian tidak berguna seperti yang diharapkan:

int Gunc() throw();    // will throw nothing (?)
int Hunc() throw(A,B); // can only throw A or B (?)

Apakah komentarnya benar? Tidak terlalu.Gunc()mungkin memang melempar sesuatu, dan Hunc()mungkin melempar sesuatu selain A atau B! Kompiler hanya menjamin untuk mengalahkan mereka tanpa alasan jika mereka melakukannya ... oh, dan untuk mengalahkan program Anda juga tidak masuk akal, sebagian besar waktu.

Itulah yang terjadi, Anda mungkin hanya akan berakhir dengan panggilan terminate() dan program Anda mati dengan cepat tetapi menyakitkan.

Kesimpulan GOTW adalah:

Jadi, inilah saran terbaik yang kami pelajari sebagai komunitas saat ini:

  • Moral # 1: Jangan pernah menulis spesifikasi pengecualian.
  • Moral # 2: Kecuali mungkin yang kosong, tetapi jika aku jadi kamu, aku akan menghindari itu.
sth
sumber
1
Saya tidak yakin mengapa saya akan melemparkan pengecualian dan tidak akan bisa menyebutkannya. Bahkan jika itu dilemparkan oleh fungsi lain saya tahu pengecualian apa yang bisa dilemparkan. Satu-satunya alasan saya bisa melihat adalah karena itu membosankan.
MasterMastic
3
@ Ken: Intinya adalah menulis spesifikasi pengecualian sebagian besar memiliki konsekuensi negatif. Satu-satunya efek positif adalah menunjukkan kepada programmer apa pengecualian dapat terjadi, tetapi karena itu tidak diperiksa oleh kompiler dengan cara yang masuk akal itu rentan terhadap kesalahan dan karena itu tidak bernilai banyak.
sth
1
Oh oke, terima kasih sudah merespons. Saya kira itulah gunanya dokumentasi.
MasterMastic
2
Tidak benar. Spesifikasi pengecualian harus ditulis, tetapi idenya adalah untuk mengkomunikasikan kesalahan apa yang harus ditangkap oleh penelepon.
HelloWorld
1
Seperti yang dikatakan @StudentT: itu adalah fungsi tanggung jawab untuk menjamin tidak membuang lebih lanjut pengecualian lainnya. Jika ya, program akan berakhir sebagaimana mestinya. Untuk menyatakan melempar berarti itu bukan tanggung jawab saya untuk menangani situasi ini dan penelepon harus memiliki informasi yang cukup untuk melakukan ini. Tidak menyatakan pengecualian berarti mereka dapat terjadi di mana saja dan dapat ditangani di mana saja. Itu pasti anti-OOP berantakan. Ini adalah kegagalan desain untuk menangkap pengecualian di tempat yang salah. Saya sarankan untuk tidak membuang kosong, karena pengecualian luar biasa dan sebagian besar fungsi tetap kosong.
Jan Turoň
30

Untuk menambahkan nilai lebih sedikit ke semua jawaban lain untuk pertanyaan ini, seseorang harus menginvestasikan beberapa menit dalam pertanyaan: Apa output dari kode berikut?

#include <iostream>
void throw_exception() throw(const char *)
{
    throw 10;
}
void my_unexpected(){
    std::cout << "well - this was unexpected" << std::endl;
}
int main(int argc, char **argv){
    std::set_unexpected(my_unexpected);
    try{
        throw_exception();
    }catch(int x){
        std::cout << "catch int: " << x << std::endl;
    }catch(...){
        std::cout << "catch ..." << std::endl;
    }
}

Jawaban: Seperti disebutkan di sini , program memanggil std::terminate()dan karenanya tidak ada penangan pengecualian yang dipanggil.

Detail: my_unexpected()Fungsi pertama dipanggil, tetapi karena tidak melempar kembali tipe pengecualian yang cocok untuk throw_exception()prototipe fungsi, pada akhirnya, std::terminate()disebut. Jadi output lengkapnya terlihat seperti ini:

user @ user: ~ / tmp $ g ++ -o exception.test kecuali.test.cpp
user @ user: ~ / tmp $ ./except.test
yah - ini adalah
penghentian yang tidak terduga yang dipanggil setelah melempar instance 'int'
Dibatalkan (core dibuang)

John Doe
sumber
12

Satu-satunya efek praktis dari specifier lemparan adalah bahwa jika sesuatu yang berbeda dari myExcdilemparkan oleh fungsi Anda,std::unexpected akan dipanggil (bukan mekanisme pengecualian unhandled yang normal).

Untuk mendokumentasikan jenis pengecualian yang dapat dilempar suatu fungsi, saya biasanya melakukan ini:

bool
some_func() /* throw (myExc) */ {
}
Paolo Tedesco
sumber
5
Penting juga untuk dicatat bahwa panggilan ke std :: tak terduga () biasanya menghasilkan panggilan ke std :: terminate () dan akhir yang tiba-tiba dari program Anda.
sth
1
- dan MSVC setidaknya tidak menerapkan perilaku ini sejauh yang saya tahu.
jalf
9

Ketika spesifikasi lemparan ditambahkan ke bahasa itu dengan niat terbaik, tetapi praktik telah menghasilkan pendekatan yang lebih praktis.

Dengan C ++, aturan umum saya adalah hanya menggunakan spesifikasi lemparan untuk menunjukkan bahwa metode tidak dapat melempar. Ini jaminan yang kuat. Kalau tidak, anggap itu bisa melempar apa saja.

Greg D
sumber
9

Nah, saat googling tentang spesifikasi lemparan ini, saya melihat artikel ini: - ( http://blogs.msdn.com/b/larryosterman/archive/2006/03/22/558390.aspx )

Saya juga mereproduksi bagiannya di sini, sehingga dapat digunakan di masa depan terlepas dari fakta bahwa tautan di atas berfungsi atau tidak.

   class MyClass
   {
    size_t CalculateFoo()
    {
        :
        :
    };
    size_t MethodThatCannotThrow() throw()
    {
        return 100;
    };
    void ExampleMethod()
    {
        size_t foo, bar;
        try
        {
            foo = CalculateFoo();
            bar = foo * 100;
            MethodThatCannotThrow();
            printf("bar is %d", bar);
        }
        catch (...)
        {
        }
    }
};

Ketika kompiler melihat ini, dengan atribut "throw ()", kompiler dapat sepenuhnya mengoptimalkan variabel "bar", karena ia tahu bahwa tidak ada cara untuk pengecualian dilemparkan dari MethodThatCannotThrow (). Tanpa atribut throw (), kompiler harus membuat variabel "bar", karena jika MethodThatCannotThrow melempar pengecualian, handler pengecualian dapat / akan tergantung pada nilai variabel bar.

Selain itu, alat analisis kode sumber seperti prefast can (dan akan) menggunakan anotasi throw () untuk meningkatkan kemampuan deteksi kesalahan mereka - misalnya, jika Anda memiliki try / catch dan semua fungsi yang Anda panggil ditandai sebagai throw (), Anda tidak perlu mencoba / menangkap (ya, ini memiliki masalah jika nanti Anda memanggil fungsi yang bisa melempar).

Pratik Singhal
sumber
3

Spesifikasi tanpa lemparan pada fungsi inline yang hanya mengembalikan variabel anggota dan tidak mungkin melempar pengecualian dapat digunakan oleh beberapa kompiler untuk melakukan pesimisasi (kata yang dibuat untuk kebalikan dari optimasi) yang dapat memiliki efek buruk pada kinerja. Ini dijelaskan dalam literatur Boost: Pengecualian-spesifikasi

Dengan beberapa kompiler, spesifikasi tanpa-lemparan pada fungsi-fungsi non-inline mungkin bermanfaat jika optimasi yang benar dilakukan dan penggunaan fungsi tersebut berdampak pada kinerja dengan cara yang dibenarkan.

Bagi saya kedengarannya seperti apakah menggunakannya atau tidak adalah panggilan yang dilakukan oleh mata yang sangat kritis sebagai bagian dari upaya optimalisasi kinerja, mungkin menggunakan alat profil.

Kutipan dari tautan di atas untuk mereka yang tergesa-gesa (berisi contoh efek buruk yang tidak diinginkan dengan menetapkan lemparan pada fungsi sebaris dari kompiler naif):

Dasar pemikiran pengecualian-spesifikasi

Spesifikasi pengecualian [ISO 15.4] kadang-kadang diberi kode untuk menunjukkan pengecualian apa yang mungkin dilemparkan, atau karena programmer berharap mereka akan meningkatkan kinerja. Tetapi pertimbangkan anggota berikut dari penunjuk cerdas:

T & operator * () const throw () {return * ptr; }

Fungsi ini tidak memanggil fungsi lain; itu hanya memanipulasi tipe data fundamental seperti pointer Karena itu, tidak ada perilaku runtime dari pengecualian-spesifikasi yang dapat dipanggil. Fungsi ini sepenuhnya terpapar ke kompiler; memang itu dinyatakan inline. Oleh karena itu, kompiler pintar dapat dengan mudah menyimpulkan bahwa fungsi tidak mampu melempar pengecualian, dan melakukan optimasi yang sama akan dibuat berdasarkan pada pengecualian-spesifikasi kosong. Namun, kompiler "bodoh" dapat membuat semua jenis pesimis.

Sebagai contoh, beberapa kompiler mematikan inlining jika ada pengecualian-spesifikasi. Beberapa kompiler menambahkan blok coba / tangkap. Pesimisasi semacam itu dapat menjadi bencana kinerja yang membuat kode tidak dapat digunakan dalam aplikasi praktis.

Meskipun awalnya menarik, pengecualian-spesifikasi cenderung memiliki konsekuensi yang memerlukan pemikiran yang sangat cermat untuk dipahami. Masalah terbesar dengan pengecualian-spesifikasi adalah bahwa programmer menggunakannya seolah-olah mereka memiliki efek yang diinginkan oleh programmer, daripada efek yang sebenarnya mereka miliki.

Fungsi non-inline adalah satu tempat pengecualian-"melempar apa-apa"-spesifikasi mungkin memiliki beberapa manfaat dengan beberapa kompiler.

Pablo Adames
sumber
1
"Masalah terbesar dengan pengecualian-spesifikasi adalah bahwa programmer menggunakannya seolah-olah mereka memiliki efek yang diinginkan oleh programmer, daripada efek yang sebenarnya mereka miliki." Inilah alasan # 1 untuk kesalahan, ketika berkomunikasi dengan mesin atau orang: perbedaan antara apa yang kita katakan dan apa yang kita maksudkan.
Thagomizer