Tangkap dengan referensi ketika variabel pengecualian tidak ditentukan

Saat menangkap pengecualian, panduan standarnya adalah membuang berdasarkan nilai, menangkap dengan referensi. Sejauh yang saya pahami, ini karena dua alasan:

  1. Jika pengecualian dilempar karena pengecualian kehabisan memori, kami tidak akan memanggil konstruktor salinan yang berpotensi menghentikan program.
  2. Jika pengecualian adalah bagian dari hierarki warisan, kita mungkin berpotensi melakukan pemotongan objek pada pengecualian tersebut.

Jika kita memiliki skenario di mana kita tidak mendefinisikan nama pengecualian di blok catch, apakah kekhawatiran ini (benarkah 1., karena pemotongan tidak akan menjadi masalah jika kita tidak memiliki nama untuk variabelnya) masih valid?

Misalnya:

catch(my_exception)
{ ... }

or

catch(my_exception &)
{ ... }

Apakah masih ada kemungkinan program dihentikan jika pengecualian tertangkap oleh nilai dalam kasus ini? Menurut saya secara teknis hal itu masih memungkinkan.

Catatan: Saya menanyakan hal ini karena saya harus meninjau kode seseorang yang memberikan nilai tangkapan dalam kasus ini. Seperti yang ditunjukkan dalam pertanyaan, saya tidak sepenuhnya yakin dengan dampak teknis dari kedua pilihan tersebut, tetapi menurut saya dalam hal konsistensi, lebih baik menangkap dengan referensi dalam hal ini (tidak ada kerugian untuk menangkap dengan referensi dalam hal apa pun) .


person Christopher Howlin    schedule 13.09.2011    source sumber
comment
Meskipun menangkap my_exception& merupakan praktik standar, saya menemukan bahwa sebagian besar pengecualian tidak diubah, dan oleh karena itu akan lebih baik untuk menangkap my_exception const&. Perhatikan bahwa what adalah const.   -  person Matthieu M.    schedule 13.09.2011
comment
Pertanyaan ini mengilhami pertanyaan ini tentang copy elision dalam kasus seperti ini: stackoverflow.com/questions/7401521/   -  person Potatoswatter    schedule 13.09.2011


Jawaban (3)


Standar ini tidak memerlukan optimasi khusus dalam kasus objek pengecualian yang tidak disebutkan namanya. Sebaliknya, hal ini memerlukan efek seolah-olah salinan sementara diinisialisasi. Penyalinan ini dapat mengakibatkan memori dialokasikan secara dinamis.

N3290 §15.3/16:
Jika deklarasi-pengecualian menentukan nama, ia mendeklarasikan variabel yang diinisialisasi salinan (8.5) dari objek pengecualian. Jika deklarasi-pengecualian menunjukkan tipe objek tetapi tidak menentukan nama, sementara (12.2) akan diinisialisasi salinan (8.5) dari objek pengecualian. Masa hidup variabel atau sementara berakhir ketika handler keluar, setelah penghancuran objek otomatis apa pun yang diinisialisasi dalam handler.

Paragraf di atas tidak menyebutkan penangkapan dengan referensi, sehingga dapat disimpulkan secara masuk akal bahwa hal tersebut berlaku terlepas dari apakah objek pengecualian ditangkap dengan referensi atau tidak; bahwa salinannya tetap dibuat.

Namun hal tersebut bertentangan dengan paragraf berikutnya:

N3290 §15.3/17:
Saat handler mendeklarasikan objek non-konstan, perubahan apa pun pada objek tersebut tidak akan memengaruhi objek sementara yang diinisialisasi dengan eksekusi ekspresi lemparan . Saat handler mendeklarasikan referensi ke objek non-konstan, setiap perubahan pada objek yang direferensikan adalah perubahan pada objek sementara yang diinisialisasi ketika ekspresi lemparan dijalankan dan akan berpengaruh jika objek tersebut dilempar kembali.

Jadi, tipe yang dideklarasikan T& (dengan T non-const) adalah satu-satunya kasus di mana C++11 memerlukan referensi langsung ke objek yang dilempar, alih-alih menyalin. Dan hal yang sama juga terjadi pada C++03, hanya saja C++03 memiliki beberapa kata tambahan tentang optimasi as-if. Jadi, untuk yang formal sebaiknya diutamakan

    catch( T& name )

Dan

    catch( T& )

Namun, saya selalu menemukan pengecualian seperti catch( T const& ). Dari sudut pandang praktis seseorang dapat berasumsi bahwa kompiler akan mengoptimalkannya ke referensi langsung ke objek yang dilempar, meskipun ada kemungkinan untuk merancang kasus di mana efek program yang diamati akan menjadi non-standar. Misalnya <evil grin>

#include <stdio.h>
#include <stdexcept>

struct Error
    : std::runtime_error
{
public:
    static Error* pThrown;

    char const* pMessage_;
    Error()
        : std::runtime_error( "Base class message" )
        , pMessage_( "Original message." )
    {
        printf( "Default-construction of Error object.\n" );
        pThrown = this;
    }

    Error( Error const& other )
        : std::runtime_error( other )
        , pMessage_( other.pMessage_ )
    {
        printf( "Copy-construction of Error obejct.\n" );
    }

    char const* what() const throw() { return pMessage_; }
};

Error*  Error::pThrown  = 0;

int main()
{
    printf( "Testing non-const ref:\n" );
    try
    {
        throw Error();
    }
    catch( Error& x )
    {
        Error::pThrown->pMessage_ = "Modified message.";
        printf( "%s\n", x.what() );
    }

    printf( "\n" );
    printf( "Testing const ref:\n" );
    try
    {
        throw Error();
    }
    catch( Error const& x )
    {
        Error::pThrown->pMessage_ = "Modified message";
        printf( "%s\n", x.what() );
    }
}

Dengan MinGW g++ 4.4.1 dan Visual C++ 10.0, hasilnya adalah

Testing non-const ref:
Default-construction of Error object.
Modified message.

Testing const ref:
Default-construction of Error object.
Modified message

Seorang formalis yang bertele-tele mungkin mengatakan bahwa kedua kompiler tidak sesuai, gagal membuat salinan untuk kasus Error const&. Seorang praktisi yang murni praktis mungkin berkata, hei, apa lagi yang Anda harapkan? Dan menurut saya, kata-kata dalam standar sangat jauh dari sempurna di sini, dan jika ada, kita harus mengharapkan klarifikasi untuk secara eksplisit mengizinkan keluaran di atas, sehingga penangkapan dengan referensi ke const juga aman dan efisien secara maksimal.

Kesimpulannya. pertanyaan OP:

  • Menangkap dengan referensi tidak akan memanggil konstruktor salinan yang berpotensi menghentikan program.

  • Standar ini hanya menjamin hal ini untuk referensi ke non-const.

  • Dalam praktiknya, seperti yang ditunjukkan, hal ini juga dijamin untuk referensi ke const, bahkan ketika hasil program terpengaruh.

Salam & hth.,

person Cheers and hth. - Alf    schedule 13.09.2011

Saya lebih suka menangkap dengan referensi. Kompiler dapat membuang pengecualian tersebut sebagai optimasi dan tidak melakukan penyalinan, namun itu hanya kemungkinan. Menangkap dengan referensi memberi Anda kepastian.

person R. Martinho Fernandes    schedule 13.09.2011
comment
Pertanyaan: dapatkah kompiler mengoptimalkan salinan meskipun konstruktor salinan pengecualian memiliki efek samping? - person R. Martinho Fernandes; 13.09.2011

Mungkin ada data tak terlihat yang terkait dengan pengecualian Anda, mis. sebuah vtable.

Vtable adalah struktur data tak terlihat yang membawa informasi tentang di mana fungsi anggota polimorfik tertentu (yaitu virtual) dapat ditemukan. Tabel ini pada umumnya memerlukan sedikit memori yang disimpan dalam objek itu sendiri. Ini mungkin karena ukuran pointer ke beberapa tabel eksternal, atau bahkan tabel lengkap. Seperti biasa, itu tergantung.

person Sebastian Mach    schedule 13.09.2011
comment
Bisakah Anda menjelaskan lebih lanjut apa maksudnya dalam konteks ini? - person Christopher Howlin; 13.09.2011