Bab. 8 Penanganan Eksepsi
Tujuan
Instruksional
|
|
·
Pembagian oleh nol.
·
Kapan menggunakan penanganan eksepsi.
·
Melempar-ulang eksespi.
|
·
Konstruktor, destruktor, dan penanganan eksepsi.
·
Memproses kegagalan new.
·
Kelas unique_ptr dan alokasi
memori dinamis.
|
8.1 Introduksi
Seperti yang Anda ketahui, eksepsi
adalah sebuah indikasi masalah yang terjadi selama eksekusi program. Penanganan
eksepsi memampukan Anda untuk menciptakan aplikasi yang dapat menangani
eksepsi. Pada banyak kasus, penanganan sebuah eksepsi memampukan program untuk
melanjutkan eksekusi seperti tidak terjadi masalah apapun. Fitur yang disajikan
pada bab ini memampukan Anda untuk menulis program yang handal dan toleran
terhadap kegagalan.
Akan dimulai dengan mereview konsep
penanganan eksepsi melalui sebuah contoh yang mendemonstrasikan penanganan
sebuah eksepsi ketika suatu fungsi mencoba untuk melakukan pembagian oleh nol.
Akan ditunjukkan bagaimana menangani eksepsi yang terjadi di dalam sebuah
konstruktor atau destruktor dan bagaimana menangani eksepsi jika operator new gagal mengalokasikan memori untuk
sebuah objek. Juga akan dikenalkan beberapa kelas penanganan eksepsi dalam
pustaka C++.
8.2 Contoh: Penanganan Pembagian oleh
Nol
Sekarang
akan didiskusikan sebuah contoh penanganan eksepsi (Gambar 8.1 - 8.2). Akan
ditunjukkan bagaimana menangani masalah aritmatika umum, yaitu pembagian oleh
nol. Dalam C++, pembagian oleh nol menggunakan aritmatika integer secara umum
menyebabkan sebuah program berhenti secara prematur. Dalam aritmatika pecahan,
beberapa implementasi C++ mengijinkan pembagian oleh nol, dimana pada kasus itu
akan dihasilkan infinitas negatif atau positif yang ditampilkan sebagai INF
atau –INF.
Pada
contoh ini, akan didefinisikan sebuah fungsi bernama quotient yang menerima dua masukan integer oleh pengguna dan
membagi argumen int pertamanya dengan
argumen int keduanya. Sebelum
melakukan pembagian, fungsi mengcast
nilai argumen int pertamanya menjadi
tipe double. Kemudian, nilai
parameter int keduanya dipromosikan
secara implisit menjadi tipe double.
Sehingga fungsi quotient sebenarnya
melakukan pembagian atas dua nilai double
dan menghasilkan nilai balik double.
Meskipun
pembagian oleh nol seringkali diijinkan dalam aritmatika pecahan, untuk
kepentingan contoh ini sembarang pembagian oleh nol dianggap sebagai error.
Fungsi quotient menguji parameter
keduanya untuk memastikan bahwa ia bernilai tak-nol sebelum melakukan
pembagian. Jika parameter kedua bernilai nol, fungsi melemparkan sebuah eksepsi
untuk mengindikasikan kepada pemanggil bahwa suatu masalah telah terjadi.
Pemanggil (main pada contoh ini)
kemudian dapat memproses eksepsi dan mengijinkan pengguna untuk mengetikkan dua
nilai baru sebelum memanggil fungsi quotient.
Dengan cara ini, program dapat melanjutkan eksekusi meskipun nilai yang tidak
tepat dientrikan. Inilah fitur yang membuat program ini handal atau robust.
Contoh
ini memuat dua file. EksepsiPembagianOlehNol.h
(Gambar 8.1) mendefinisikan sebuah kelas eksepsi yang merepresentasikan tipe
masalah yang mungkin terjadi pada masalah ini, dan gambar8_02.cpp (Gambar 8.2) mendefinisikan fungsi quotient dan fungsi main yang memanggilnya. Fungsi main
memuat kode yang mendemonstrasikan penanganan eksepsi.
Mendefinisikan
Suatu Kelas Eksepsi
Gambar
8.1 mendefinisikan kelas EksepsiPembagianOlehNol.h
sebagai sebuah kelas terderivasi dari kelas Pustaka Standard runtime_error (didefinisikan dalam
header <stdexcept>). Kelas runtime_error, sebuah kelas terderivasi
dari kelas Pustaka Standard exception (didefinisikan dalam header <exception>), merupakan kelas basis
standard C++ untuk merepresentasikan error runtime. Sebuah kelas eksepsi yang
diderivasi dari kelas runtime_error
hanya mendefinisikan sebuah konstruktor (baris 12-13) yang melewatkan sebuah
string pesan-error kepada konstruktor kelas basis runtime_error. Setiap kelas eksepsi yang diderivasi secara langsung
atau tak-langsung dari kelas exception
memuat fungsi virtual, what, yang menghasilkan nilai balik
berupa sebuah pesan-error pada suatu objek. Anda tidak diharuskan untuk
menderivasi kelas eksepsi buatan sendiri, seperti EksepsiPembagianOlehNol, dari kelas eksepsi standard yang
disediakan C++. Namun, jika melakukannya Anda dapat menggunakan fungsi virtual, what, untuk mendapatkan pesan error yang tepat. Anda dapat
menggunakan objek dari kelas EksepsiPembagianOlehNol
pada Gambar 8.2 untuk mengindikasikan bahwa telah terjadi pembagian oleh nol.
Gambar 8.1 Definisi kelas EksepsiPembagianOlehNol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
// Gambar 8.1: EksepsiPembagianOlehNol.h
// Definisi kelas EksepsiPembagianOlehNol.
#include <stdexcept> // header stdexcept yang memuat
runtime_error
using namespace std;
// objek EksepsiPembagianOlehNol harus
dilemparkan oleh fungsi
// yang mendeteksi pembagian-olh-nol
class EksepsiPembagianOlehNol : public runtime_error
{
public:
//
konstruktor menspesifikasi pesan error default
EksepsiPembagianOlehNol()
runtime_error( "pencobaan
untuk membagi oleh nol" )
}; // akhir dari kelas
EksepsiPembagianOlehNol
|
Mendemonstrasikan
Penanganan Eksepsi
Gambar
8.2 menggunakan penanganan eksepsi untuk kode yang kemungkinan melempar sebuah
eksepsi “pembagian-oleh-nol” dan menangani eksepsi tersebut. Pengguna
memasukkan dua integer, yang dilewatkan sebagai argumen kepada fungsi quotient (baris 10-18). Fungsi ini membagi
parameter pertamanya (numerator) oleh
parameter keduanya (denominator).
Jika pengguna tidak menspesifikasi 0 sebagai denominator untuk pembagian, maka
fungsi quotient menghasilkan hasil
pembagian. Jika pengguna memasukkan 0 untuk denominator, maka quotient akan melemparkan sebuah
eksepsi. Pada contoh keluaran, dua baris pertama menunjukkan kalkulasi
pembagian yang sukses, dan dua baris berikutnya menunjukkan kegagalan pembagian
karena mencoba melakukan pembagian oleh nol. Ketika eksepsi terjadi, program
menginformasikan pengguna tentang kesalahan yang dilakukan dan mendesak
pengguna untuk memasukkan kembali dua integer baru.
Gambar 8.2 Contoh Penanganan Eksespi Sederhana
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
// Gambar 8.2: gambar8_02.cpp
// Contoh penanganan-eksepsi sederhana yang
memeriksa
// eksepsi pembagian-oleh-nol.
#include <iostream>
#include "EksepsiPembagianOlehNol.h" // kelas
EksepsiPembagianOlehNol
using namespace std;
// melakukan pembagian dan melempar objek
EksepsiPembagianOlehNol jika
// terjadi eksepsi pembagian-oleh-nol
double quotient( int numerator, int
denominator )
{
// melempar EksepsiPembagianOlehNol jika
mencoba membagi oleh nol
if
( denominator == 0 )
throw EksepsiPembagianOlehNol(); //
menghentikan fungsi
// memberikan hasil pembagian
return static_cast< double
>( numerator ) / denominator;
} // akhir dari fungsi quotient
int main()
{
int angka1; // numerator
int angka2; // denominator
double hasil; // hasil pembagian
cout << "Masukkan
dua integer (end-of-file untuk mengakhiri): ";
// memampukan pengguna memasukkan dua
integer untuk dibagi
while ( cin >> angka1 >>
angka2 )
{
//
blok try memuat kode yang bisa melempar eksepsi
//
dan kode yang tidak akan dieksekusi jika eksepsi terjadi
try
{
hasil
= quotient( angka1, angka2 );
cout
<< "Quotient adalah: " << hasil << endl;
}
// akhir dari try
catch
( EksepsiPembagianOlehNol &eksepsiPembagianOlehNol )
{
cout
<< "Eksepsi terjadi: "
<< eksepsiPembagianOlehNol.what() << endl;
}
// akhir dari catch
cout
<< "\nMasukkan dua integer (end-of-file untuk mengakhiri): ";
} //
akhir dari while
cout
<< endl;
} // akhir dari main
|
Masukkan
dua integer (end-of-file untuk mengakhiri):100 7
Quotient
adalah: 14.2857
Masukkan
dua integer (end-of-file untuk mengakhiri): 100 0
Eksepsi
terjadi: pencobaan untuk membagi oleh nol
Masukkan
dua integer (end-of-file untuk mengakhiri): ^Z
Mengapit Kode di
dalam Blot try
Program
memulai eksekusi dengan meminta pengguna untuk memasukkan dua integer. Integer
dimasukkan di dalam kondisi loop while
(baris 29). Baris 35 melewatkan kedua nilai tersebut kepada fungsi quotient (baris 10-18), yang melakukan
pembagian dan memberikan hasil pembagian atau melemparkan sebuah eksespi (baris
10-18) (mengindikasikan terjadinya suatu error) karena mencoba untuk membagi
dengan nol. Penanganan eksepsi dilakukan pada situasi dimana fungsi mendeteksi
error tetapi tidak dapat mengatasinya.
Blok
try dimaksudkan untuk menangani
eksepsi. Blok try mengapit
statemen-statemen yang dapat menyebabkan eksepsi dan statemen-statemen yang
harus dilompati jika eksepsi terjadi. Blok try
pada baris 33-37 mengapit pemanggilan fungsi quotient dan statemen yang menampilkan hasil pembagian. Pada contoh
ini, karena pemanggilan fungsi quotient
(baris 35) kemungkinan bisa melemparkan sebuah eksepsi, maka fungsi ini diapit
di dalam suatu blok try. Pengapitan
statemen keluaran (baris 36) di dalam blok try
dilakukan untuk memastikan bahwa keluaran terjadi hanya jika fungsi quotient memberikan nilai balik (hasil
pembagian).
Mendefinisikan
Handler catch untuk Memproses EksepsiPembagianOlehNol
Sedikitnya
terdapat satu handler catch (baris
38-42) yang harus disediakan mengikuti setiap blok try. Parameter eksepsi dideklarasikan sebagai sebuah referensi yang
menunjuk ke tipe eksepsi yang dapat diproses oleh handler catch (pada kasus ini EksepsiPembagianOlehNol).
Ketika eksepsi terjadi di dalam blok try,
handler catch yang dieksekusi adalah
yang memiliki tipe yang cocok dengan tipe eksepsi yang terjadi. Jika paramate
eksepsi menyertakan sebuah nama parameter opsional, maka handler catch dapat menggunakan nama parameter
itu untuk berinteraksi dengan eksepsi yang ditangkap di dalam tubuh handler catch, yang dibatasi oleh kurung kurawal
({ dan }). Handler catch secara umum
melaporkan error kepada pengguna, menyimpannya di dalam sebuah file, dan
memberhentikan program secara normal atau mencoba melakukan strategi alternatif
dalam melakukan tugas yang tadinya sudah gagal. Pada contoh ini, handler catch hanya melaporkan kepada pengguna
tentang pencobaan pembagian oleh nol. Kemudian program meminta pengguna untuk
mengentrikan dua integer yang baru.
Mendefinisikan
Handler catch untuk Memproses EksepsiPembagianOlehNol
Jika
suatu eksepsi terjadi sebagai hasil dari sebuah statemen di dalam blok try, maka blok try tersebut berhenti dengan segera. Berikutnya, program mencari
handler catch pertama yang dapat
memproses tipe eksepsi yang terjadi. Program mencari lokasi handler catch yang cocok dengan cara
membandingkan tipe eksepsi yang dilempar dengan setiap tipe parameter eksespi
dari handler catch sampai program menemukan kecocokan. Kecocokan terjadi jika
tipe identik atau jika tipe eksepsi yang dilempar adalah tipe parameter eksepsi
dari kelas terderivasi. Ketikan kecocokan terjadi, kode yang dimuat di dalam
handler catch yang cocok dieksekusi. Ketika
handler catch tersebut selesai
dieksekusi (karena telah mencapai kurung kurawal penutup (})), eksepsi dianggap
telah ditangani dan semua variabel lokal yang didefinisikan di dalam handler catch (termasuk parameter catch) dihapus dari memori. Kendali
program tidak kembali ke titik dimana eksepsi terjadi (dikenal sebagai titik
pelemparan), karena blok try sudah
tidak ada lagi (sudah tidak eksis). Kendali program akan pergi ke statemen
pertama (baris 44) setelah handler catch
terakhir yang mengikuti blok try. Hal
ini dikenal dengan model terminasi penanganan eksepsi.
Jika
blok try selesai dieksekusi secara
sukses (tidak ada eksepsi yang terjadi di dalam blok try), maka program akan mengabaikan semua handler catch dan kendali program pergi ke
statemen pertama setelah handler catch
terakhir yang mengikuti blok try
tersebut.
Jika
sebuah eksepsi yang terjadi di dalam suatu blok try tidak cocok dengan semua handler catch yang ada, atau jika sebuah eksepsi terjadi di dalam statemen
yang tidak berada di dalam sebuah try,
maka fungsi yang memuat statemen tersebut berhenti dengan segera, dan program
mencoba mencari lokasi blok penutup try
di dalam fungsi pemanggil. Proses ini dikenal dengan penguraian tumpukan.
Aliran Kendali
Program Ketika Pengguna Memasukkan Denominator Tak-Nol
Perhatikan
aliran kendali program ketika pengguna memasukkan numerator 100 dan denominator
7. Pada baris 13, fungsi quotient
menentukan bahwa denominator tidak
sama dengan nol, sehingga baris 17 melakukan pembagian dan memberikan hasil
(14.2857) kepada baris 35 sebagai suatu double.
Kendali program kemudian berlanjut secara sekuensial dari baris 35, sehingga
baris 36 menampilkan hasil pembagian dan baris 37 mengakhiri blok try. Karena blok try dieksekusi dengan sukses dan tidak melempar sebuah eksepsi,
program tidak mengeksekusi statemen-statemen yang dimuat di dalam handler catch (baris 38-42), dan kendali
berlanjut ke baris 44 (baris pertama setelah handler catch), yang meminta pengguna untuk memasukkan dua integer baru.
Aliran Kendali
Program Ketika Pengguna Memasukkan Denominator Nol
Sekarang
perhatikan kasus dimana pengguna memasukkan numerator 100 dan denominator 0.
Pada baris 13, quotient menentukan bahwa
denominator sama dengan nol, yang
mengindikasikan suatu percobaan untuk membagi oleh nol. Baris 14 melempar
sebuah eksepsi, yang direpresentasikan oleh sebuah objek dari kelas EksepsiPembagianOlehNol (Gambar 8.1).
Untuk
melemparkan sebuah eksepsi, baris 14 menggunakan katakunci throw yang diikuti oleh sebuah operand yang merepresentasikan tipe
eksepsi yang dilempar. Normalnya, sebuah statemen throw menspesifikasi satu
operand. Operand suatu throw bisa
berupa sembarang tipe. Jika operand adalah sebuah objek, maka ia dipanggil
dengan objek eksepsi. Pada contoh ini, objek eksepsi adalah sebuah objek
bertipe EksepsiPembagianOlehNol. Operand
suatu throw dapat pula berupa nilai
suatu ekspresi yang tidak dihasilkan di dalam objek suatu kelas (seperti throw x > 5) atau berupa nilai suatu
int (misalnya, throw 5). Contoh pada bab ini akan difokuskan secara ekslusif
pada pelemparan objek dari kelas eksepsi.
Sebagai
bagian dari sebuah eksepsi, operand throw
diciptakan dan digunakan untuk menginisialisasi parameter di dalam handler catch.
Statemen throw pada baris 14
menciptakan objek EksepsiPembagianOlehNol.
Ketika baris 14 melempar eksepsi, fungsi quotient
berhenti dengan segera. Jadi, baris 14 melempar eksepsi sebelum fungsi quotient melakukan pembagian pada baris
17. Ini merupakan karakteristik sentral
dari penanganan eksepsi: Suatu fungsi harus melempar eksepsi sebelum error
sempat terjadi.
Karena
pemanggilan fungsi quotient (baris
35) diapit di dalam suatu blok try,
kendali program memasuki handler catch
(baris 38-42) yang mengikuti blok try.
Handler catch ini berperan sebagai
handler eksepsi untuk eksepsi pembagian-oleh-nol. Secara umum, ketika eksepsi
dilempar di dalam sebuah blok try,
eksepsi itu ditangkap oleh sebuah handler catch
yang menspesifikasi tipe yang cocok dengan eksepsi yang dilempar. Pada program
ini, handler catch menspesifikasi dan
menangkap objek EksepsiPembagianOlehNol.
Tubuh
catch (baris 40-41) menampilkan pesan
error yang dijadikan nilai balik oleh fungsi what dari kelas basis runtime_error.
Nilai balik itu berupa string yang dilewatkan oleh konstruktor EksepsiPembagianOlehNol kepada
konstruktor kelas basis runtime_error.
8.3 Melempar-Ulang Eksepsi
Adalah
hal yang mungkin bahwa suatu handler eksepsi, setelah menerima sebuah eksepsi,
untuk memutuskan bahwa ia tidak bisa memproses eksepsi tersebut atau ia hanya
dapat memproses eksepsi itu secara parsial. Pada kasus semacam itu, handler
eksepsi dapat mengalihkan penanganan eksepsi itu kepada handler eksepsi lain.
Hal ini dilakukan dengan melemparkan-ulang eksepsi melalui statemen
throw;
Program
pada Gambar 8.3 mendemonstrasikan pelemparan-ulang sebuah eksepsi. Pada blok try di dalam main (baris 29-34), baris 32 memanggil fungsi lemparEksepsi (baris 8-24). Fungsi lemparEksepsi juga memuat sebuah blok try (baris 11-15), dimana statemen throw pada baris 14 melempar sebuah instans dari kelas pustaka
standard exception. Handler catch pada fungsi lemparEksepsi (baris 16-21) menangkap eksepsi ini, menampilkan
pesan error (baris 18-19) dan melemparkan kembali eksepsi tersebut (baris 20). Ini
memberhentikan fungsi lemparEksepsi
dan mengembalikan kendali program kepada baris 32 di dalam blok try...catch di dalam main. Blok try berhenti (jadi baris 33 tidak dieksekusi), dan handler catch di dalam main (baris 35-38) menangkap eksepsi ini dan menampilkan pesan
error (baris 37). Karena parameter eksepsi di dalam handler catch tidak dipakai
pada contoh ini, nama parameter diabaikan dan tipe eksepsi yang akan ditangkap
dispesifikasi (baris 16 dan 35).
Gambar 8.3 Mendemonstrasikan Pelemparan-Ulang
Eksepsi
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
// Gambar 8.3: gambar8_03.cpp
// Mendemonstrasikan pelemparan-ulang
eksepsi.
#include <iostream>
#include <exception>
using namespace std;
// melempar, menangkap dan melempar-ulang
eksepsi
void lemparEksepsi()
{
// melempar eksepsi dan menangkapnya lagi
try
{
cout
<< " Fungsi lemparEksepsi melempar sebuah eksepsi\n";
throw exception(); // membangkitkan eksepsi
} //
akhir dari try
catch ( exception & ) //
menangani eksepsi
{
cout << " Eksepsi ditangani
di dalam fungsi lemparEksepsi"
<< "\n Fungi
lemparEksepsi melempar-ulang eksepsi";
throw; // melempar-ulang eksepsi
untuk pemrosesan lanjut
} // akhir dari catch
cout << "Ini tidak ditampilkan\n";
} // akhir dari fungsi lemparEksepsi
int main()
{
// melempar eksepsi
try
{
cout << "\nmain memanggil
fungsi lemparEksepsi\n";
lemparEksepsi();
cout << "Ini tidak
ditampilkan\n";
} //
akhir dari try
catch ( exception & ) //
menangani eksepsi
{
cout << "\n\nEksepsi
ditangani di dalam main\n";
} // akhir dari catch
cout << "Kendali
program berlanjut setelah catch di dalam main\n";
} // akhir dari main
|
main
memanggil fungsi lemparEksepsi
Fungsi
lemparEksepsi melempar sebuah eksepsi
Eksepsi
ditangani di dalam fungsi lemparEksepsi
Fungi
lemparEksepsi melempar-ulang eksepsi
Eksepsi
ditangani di dalam main
Kendali
program berlanjut setelah catch di dalam main
8.4 Penguraian Tumpukan
Ketika
sebuah eksepsi dilempar tetapi tidak ditangkap di dalam skop tertentu, tumpukan
pemanggilan fungsi “terurai” dan
percobaan untuk menangkap eksepsi dilakukan di blok try...catch sebelah luar. Penguraian tumpukan pemanggilan fungsi
berarti bahwa fungsi yang di dalamnya eksepsi tidak ditangkap berhenti, semua
variabel lokal dihancurkan, dan kendali program kembali ke statemen yang semula
memanggil fungsi tersebut. Jika sebuah blok try
mengapit statemen tersebut, maka penumpukan pemanggilan terjadi lagi. Jika
tidak terdapat handler catch yang
menangkap eksepsi ini, maka fungsi terminate
dipanggil untuk menghentikan program. Program pada Gambar 8.4 mendemonstrasikan
penguraian tumpukan.
Gambar 8.4 Mendemonstrasikan Penguraian Tumpukan
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
// Gambar 8.4: gambar8_04.cpp
// Mendemonstrasikan penguraian tumpukan.
#include <iostream>
#include <stdexcept>
using namespace std;
// fungsi3 melempar error runtime
void fungsi3() throw ( runtime_error )
{
cout << "Di dalam fungsi3"
<< endl;
// tidak ada blok
try, penguraian tumpukan terjadi, kendali program kembali ke fungsi2
throw runtime_error( "runtime_error
di dalam fungsi3" );
} // akhir dari fungsi3
// fungsi2 memanggil fungsi3
void fungsi2() throw ( runtime_error )
{
cout << "fungsi3 tipanggil di
dalam fungsi2" << endl;
fungsi3(); // penguraian
tumpukan terjadi, kendali program kembali ke fungsi1
} //
akhir dari fungsi2
// fungsi1 memanggil fungsi2
void fungsi1() throw ( runtime_error )
{
cout << "fungsi2 dipanggil di
dalam fungsi1" << endl;
fungsi2(); // penguraian
tumpukan terjadi, kendali program kembali ke main
} // akhir dari fungsi1
// mendemonstrasikan penguraian tumpukan
int main()
{
// memanggil fungsi1
try
{
cout << "fungsi1 dipanggil
di dalam main" << endl;
fungsi1(); // memanggil fungsi1 yang
melempar runtime_error
} // akhir dari try
catch ( runtime_error &error ) //
menangani error runtime
{
cout << "Eksepsi terjadi:
" << error.what() << endl;
cout << "Eksepsi ditangani
di dalam main" << endl;
} //
akhir dari catch
} // akhir dari main
|
fungsi1 dipanggil di dalam main
fungsi2 dipanggil di dalam fungsi1
fungsi3 tipanggil di dalam fungsi2
Di dalam fungsi3
Eksepsi terjadi: runtime_error di dalam fungsi3
Eksepsi ditangani di dalam main
Di
dalam main, blok try (baris 34-38) memanggil fungsi1
(baris 24-28). Selanjutnya, fungsi1
memanggil fungsi2 (baris 17-21), yang
pada gilirannya memanggil fungsi3
(baris 8-14). Baris 13 pada fungsi3 melempar sebuah objek runtime_error. Namun, karena tidak ada blok try yang mengapit statemen throw
pada baris 13, penguraian tumpukan terjadi. Fungsi fungsi3 berhenti pada baris 13, kemudian mengembalikan kendali
program kepada statemen di dalam fungsi2
yang memanggil fungsi3 (baris 20). Karena
tidak ada blok try pada baris 20,
penguraian tumpukan terjadi sekali lagi. Fungsi fungsi2 berhenti pada baris 20, kemudian mengembalikan kendali
program kepada statemen di dalam fungsi1
yang memanggil fungsi2 (baris 27).
Karena tidak ada blok try yang
mengapit baris 27, penguraian tumpukan terjadi sekali lagi. Fungsi fungsi1 berhenti pada baris 27, kemudian
mengembalikan kendali program kepada statemen di dalam main yang memanggil fungsi1 (baris
37). Blok try pada baris 34-38
mengapit statemen ini, sehingga handler pertama yang cocok yang dilokasikan
setelah blok try ini (baris 39-43)
menangkap dan memproses eksepsi. Baris 41 menggunakan fungsi what untuk menampilkan pesan eksepsi.
Ingat bahwa fungsi what adalah sebuah
fungsi virtual dari kelas exception yang dioverride oleh kelas terderivasi untuk memberikan nilai balik berupa
pesan error.
8.5 Memproses Kegagalan new
Standard
C++ menspesifikasi bahwa, ketika operator new
gagal, ia akan melempar sebuah eksepsi bad_alloc
(didefinisikan di dalam header <new>).
Pada bagian ini, akan disajikan dua contoh kegagalan operator new. Contoh pertama menggunakan versi new yang melempar eksepsi bad_alloc ketika new gagal. Contoh kedua menggunakan fungsi set_new_handler untuk menangani kegagalan new. Perhatikan bahwa dua contoh pada Gambar 8.5 – 8.6
mengalokasikan sejumlah besar memori dinamis, yang menyebabkan komputer Anda
melambat.
Operator new
Melempar Eksepsi bad_alloc
Gambar
8.5 mendemonstrasikan operator new
yang melempar eksepsi bad_alloc
karena gagal mengalokasikan memori yang diminta. Statemen for (baris 16-20) di dalam blok try
beriterasi sebanyak 50 kali dan, pada setiap iterasi, mengalokasikan sebuah
array yang memuat 50 000 000 nilai double.
Jika new gagal mengalokasikan memori
yang diminta, maka ia akan melempar sebuah eksepsi bad_alloc, loop akan berhenti, dan program akan melanjutkan
eksekusi pada baris 22, dimana handler catch
menangkap dan memproses eksepsi yang dilempar. Baris 24-25 menampilkan pesan “Eksepsi terjadi: “ diikuti oleh pesan
yang dijadikan nilai balik oleh fungsi what
pada kelas basis exception (dalam
Microsoft Visual C++ akan menghasilkan pesan “Allocation Failure”). Keluaran menunjukkan bahwa program hanya
sanggup melakukan empat iterasi sebelum new
gagal dan melempar eksepsi bad_alloc.
Keluaran akan berbeda tergantung dari memori fisikal yang Anda miliki pada
sistem Anda.
Gambar 8.5 Mendemonstrasikan new Melempar bad_alloc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
// Gambar 8.5: gambar8_05.cpp
// Mendemonstrasikan new melempar bad_alloc
ketika memori
// tidak bisa dialokasikan.
#include <iostream>
#include <new> // kelas bad_alloc didefinisikan di
sini
using namespace std;
int main()
{
double *ptr[ 50 ];
// setiap ptr[i] menunjuk ke blok memori
yang besar
try
{
// mengalokasikan
memori untuk ptr[ i ]; new melempar bad_alloc jika gagal
for ( int i = 0; i < 50;
++i )
{
ptr[ i ] = new double[
50000000 ]; // bisa jadi melempar eksepsi
cout << "ptr[" <<
i << "] menunjuk ke 50 000 000 new double\n";
} // akhir dari for
} // akhir dari try
catch( bad_alloc &EksepsiAlokasiMemori )
{
cerr << "Eksepsi terjadi:
"
<< EksepsiAlokasiMemori.what()
<< endl;
} // akhir dari catch
} // akhir dari main
|
ptr[0] menunjuk ke 50 000 000 new double
ptr[1] menunjuk ke 50 000 000 new double
ptr[2] menunjuk ke 50 000 000 new double
ptr[3] menunjuk ke 50 000 000 new double
Eksepsi terjadi: bad allocation
Operator new
Menghasilkan 0 bila Gagal
Standard
C++ menspesifikasi bahwa kompiler dapat menggunakan versi lama dari new yang memberikan nilai balik 0 jika
mengalami kegagalan. Header <new>
mendefinisikan objek nothrow (bertipe
nothrow_t), yang digunakan sebagai
berikut:
double *ptr = new( nothrow ) double[ 50000000 ];
Statemen
di atas menggunakan versi new yang
tidak melempar eksepsi bad_alloc
(objek nothrow) dalam mengalokasikan
memori untuk array yang memuat 50 000 000 double.
Menangani
Kegagalan new Menggunakan Fungsi set_new_handler
Fitur
tambahan untuk menangani kegagalan new
adalah fungsi set_new_handler
(prototipe di dalam header <new>).
Fungsi ini mengambil sebagai argumennya sebuah pointer yang menunjuk ke suatu
fungsi tanpa-argumen dan yang menghasilkan nilai balik void. Pointer ini menunjuk ke fungsi yang akan dipanggil bila new gagal. Hal ini memberikan kesempatan
bagi Anda untuk melakukan pendekatan seragam dalam menangani semua kegagalan new, tanpa memandang dimana lokasi
kegagalan new terjadi di dalam
program. Begitu set_new_handler
didaftarkan sebagai handler new di
dalam program, maka operator new
tidak akan melempar eksepsi bad_alloc
bila mengalami kegagalan dan ia menyerahkan penanganan eksepsi tersebut kepada
fungsi handler new.
Jika
new mengalokasikan memori dengan
sukses, maka ia akan menghasilkan nilai balik berupa sebuah pointer yang
menunjuk ke memori tersebut. Jika new
gagal mengalokasikan memori yang diminta dan set_new_handler tidak didaftarkan sebagai fungsi handler new, maka new akan melempar eksepsi bad_alloc.
Jika new gagal mengalokasikan memori
yang diminta dan set_new_handler
telah didaftarkan sebagai fungsi handler new,
maka handler new akan dipanggil untuk
menangani eksepsi tersebut. Standard C++ menspesifikasi bahwa fungsi handler new harus melakukan salah satu dari
beberapa tugas berikut:
1) Menyediakan memori yang diminta dengan menghapus
memori teralokasi dinamis (atau meminta pengguna untuk menuntup beberapa
aplikasi lain) dan kembali ke operator new
untuk mencoba mengalokasikan memori kembali.
2) Melempar eksepsi bertipe bad_alloc.
3)
Memanggil fungsi abort atau exit (keduanya ditemukan di dalam header <cstdlib>) untuk menghentikan program.
Gambar
8.6 mendemonstrasikan set_new_handler.
Fungsi newHandlerSendiri (baris 9-13)
menampilkan pesan error (baris 11), kemudian memanggil fungsi abort (baris 12)
untuk menghentikan program. Keluaran menunjukkan bahwa loop beriterasi sampai
empat kali sebelum new gagal dan
memanggil fungsi newHandlerSendiri.
Keluaran bisa berbeda tergantung memori fisikal pada sistem Anda.
Gambar 8.6 Mendemonstrasikan Fungsi Handler
set_new_handler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
// Gambar 8.6: gambar8_06.cpp
// Mendemonstrasikan fungsi handler
set_new_handler.
#include <iostream>
#include <new> // prototipe fungsi set_new_handler
#include <cstdlib> // prototipe fungsi abort
using namespace std;
// menangani kegagalan pengalokasian memori
void newHandlerSendiri()
{
cerr
<< "newHandlerSendiri dipanggil";
abort();
} // akhir dari fungsi newHandlerSendiri
// menggunakan
set_new_handler untuk menangani kegagalan alokasi memori
int main()
{
double *ptr[ 50 ];
//
menspesifikasi bahwa newHandlerSendiri harus dipanggil
// bila
terjadi kegagalan alokasi memori
set_new_handler(
newHandlerSendiri );
//
mengarahkan setiap ptr[i] menunjuk suatu blok memori; newHandlerSendiri
//
akan dipangil bila terjadi kegagalan alokasi memori
for ( int i = 0; i < 50;
++i )
{
ptr[ i ] = new double[
50000000 ]; // bisa jadi melemparkan eksepsi
cout << "ptr["
<< i << "] menunjuk ke 50 000 000 new double\n";
} // akhir dari for
} // akhir dari main
|
ptr[0] menunjuk ke 50 000 000 new double
ptr[1] menunjuk ke 50 000 000 new double
ptr[2] menunjuk ke 50 000 000 new double
ptr[3] menunjuk ke 50 000 000 new double
newHandlerSendiri dipanggil
This application has requested the Runtime to
terminate it in an unusual way.
Please contact the application’s support team for
more information.
8.6 Kelas unique_ptr dan Alokasi Memori
Dinamis
Praktek
pemrograman yang umum dilakukan adalah mengalokasikan memori dinamis,
menugaskan alamat memori itu kepada sebuah pointer, menggunakan pointer untuk
memanipulasi memori, dan membebaskan memori dengan delete ketika memori itu tidak lagi dibutuhkan. Jika sebuah eksepsi
terjadi setelah alokasi memori berhasil dilakukan tetapi sebelum statemen delete dieksekusi, maka akan terjadi
kebocoran memori. Standard C++ menyediakan template kelas unique_ptr di dalam header <memory>
untuk menangani situasi semacam ini.
Sebuah
objek dari kelas unique_ptr
menetapkan suatu pointer yang menunjuk ke memori yang dialokasikan secara
dinamis. Ketika sebuah destruktor untuk objek unique_ptr dipanggil (misalnya, ketika objek unique_ptr keluar skop), ia melakukan operasi delete pada anggota data pointernya. Template kelas unique_ptr menyediakan dua operator
teroverload, * dan ->, sehingga objek unique_ptr
dapat digunakan seperti layaknya pointer variabel biasa. Gambar 8.9 mendemonstrasikan
sebuah objek unique_ptr yang menunjuk
ke objek dari kelas Integer (Gambar 8.7
– 8.8).
Gambar 8.7 Definisi Kelas Integer
1
2
3
4
5
6
7
8
9
10
11
12
13
|
// Gambar 8.7: Integer.h
// Definisi kelas Integer.
class Integer
{
public:
Integer( int i = 0 ); // konstruktor
default Integer
~Integer();
// Destruktor Integer
void setInteger( int i ); //
fungsi untuk menetapkan Integer
int getInteger() const; //
fungsi untuk menghasilkan nilai balik Integer
private:
int nilai;
}; //
akhir dari kelas Integer
|
Gambar 8.8 Definisi Fungsi Anggota Kelas Integer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
// Gambar 8.8: Integer.cpp
// Definisi fungsi anggota Integer.
#include <iostream>
#include "Integer.h"
using namespace std;
// Konstruktor default Integer
Integer::Integer( int i )
: nilai( i )
{
cout << "Konstruktor untuk
Integer " << nilai << endl;
} // akhir dari konstruktor Integer
// destruktor Integer
Integer::~Integer()
{
cout << "Destruktor untuk
Integer " << nilai << endl;
} // akhir dari destruktor Integer
// menetapkan nilai Integer
void Integer::setInteger( int i )
{
nilai = i;
} // akhir dari fungsi setInteger
// menghasilkan nilai Integer
int Integer::getInteger() const
{
return nilai;
} // akhir dari fungsi getInteger
|
Baris
15 pada Gambar 8.9 menciptakan objek unique_ptr,
ptrToInteger dan menginisialisasinya
dengan sebuah pointer yang menunjuk ke objek Integer teralokasi dinamis yang memuat nilai 7. Baris 8 menggunakan
operator -> teroverload (dari
kelas unique_ptr) untuk memanggil
fungsi setInteger. Baris 21
menggunakan operator * teroverload
untuk mendereferensi ptrToInteger,
kemudian menggunakan operator dot (.) untuk memanggil fungsi getInteger pada objek Integer. Seperti pointer biasa, operator
-> dan operator * dapat digunakan untuk mengakses objek yang ditunjuk oleh unique_ptr.
Karena
ptrToInteger merupakan suatu variabel
otomatis lokal di dalam main, ia
dihancurkan ketika main berhenti. Destruktor unique_ptr memaksa operasi delete pada objek Integer yang ditunjuk oleh ptrToInteger,
yang pada gilirannya memanggil destruktor kelas Integer. Memori yang ditempati Integer akan dilepaskan, tanpa
memandang bagaimana kendali program keluar dari blok. Penggunaan teknik ini
akan mencegah terjadinya kebocoran memori. Sebagai contoh, dimisalkan sebuah
fungsi menghasilkan nilai balik sebuah pointer yang menunjuk ke beberapa objek.
Jika pemanggil fungsi tersebut yang menerima pointer ini tidak melakukan
operasi delete pada objek yang
ditunjuk, maka objek itu akan dihapus secara otomatis ketika destruktor objek unique_ptr dipanggil.
Gambar 8.8 Definisi Fungsi Anggota Kelas Integer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
// Gambar 8.9: gambar8_09.cpp
// Mendemonstrasikan unique_ptr.
#include <iostream>
#include <memory>
using namespace std;
#include "Integer.h"
// menggunakan unique_ptr untuk memanipulasi
objek Integer
int main()
{
cout << "Menciptakan sebuah objek unique_ptr yang menunjuk
ke suatu Integer\n";
//
mengarahkan unique_ptr untuk menunjuk objek Integer
unique_ptr< Integer >
ptrToInteger( new Integer( 7 ) );
cout
<< "\nMenggunakan unique_ptr untuk memanipulasi Integer\n";
ptrToInteger->setInteger(
99 ); // menggunakan
unique_ptr untuk menetapkan nilai Integer
// menggunakan unique_ptr untuk mendapatkan
nilai Integer
cout << "Integer
setelah setInteger: " << ( *ptrToInteger ).getInteger()
} // akhir dari main
|
Menciptakan sebuah objek unique_ptr yang
menunjuk ke suatu Integer
Konstruktor untuk Integer
7
Menggunakan unique_ptr untuk memanipulasi
Integer
Integer setelah setInteger:
99
Destruktor
untuk Integer 99
8.7 Hirarki Eksepsi Pustaka Standard
Pengalaman
membuktikan bahwa eksepsi memiliki sejumlah kategori. Pustaka standard C++
menyertakan sebuah hirarki kelas eksepsi, yang beberapa di antaranya
ditampilkan pada Gambar 15.10. Hirarki ini mempunyai kelas basis exception (yang didefinisikan di dalam
header <exception>), memuat
fungsi virtual, what, yang dapat dioverride
oleh kelas terderivasi untuk mengeluarkan pesan error.
Yang
mewarisi kelas basis exception adalah
kelas runtime_error dan kelas logic_error (keduanya
didefinisikan di dalam header <stdexcept>),
yang masing-masing memiliki beberapa kelas terderivasi. Beberapa kelas lain
yang mewarisi exception secara langsung adalah bad_alloc yang dilemparkan oleh new,
bad_cast yang dilemparkan oleh dynamic_cast, bad_typeid yang dilemparkan oleh typeid, bad_exception
yang dilemparkan oleh unexpected.
Kelas
logic_error merupakan kelas basis
bagi beberapa kelas eksepsi standard yang mengindikasikan terjadinya error
logika di dalam program. Sebagai contoh, kelas invalid_argument mengindikasikan adanya argumen invalid yang
dilewatkan kepada sebuah fungsi. Kelas length_error
mengindikasikan bahwa suatu panjang melebihi ukuran maksimum yang diijinkan
untuk objek yang sedang dimanipulasi. Kelas out_of_range
mengindikasikan bahwa sebuah nilai, seperti subskript pada sebuah array,
melebihi rentang nilai yang diijinkan.
Kelas
runtime_error merupakan kelas basis
bagi kelas eksepsi standard lainnya yang mengindikasikan adanya error pada
waktu eksekusi. Sebagai contoh, kelas overflow_error
mendeskripsikan sebuah error overflow
aritmatik (misalnya, hasil dari operasi aritmatik lebih besar dari nilai
terbesar yang dapat disimpan di dalam komputer) dan kelas underflow_error yang mendeskripsikan sebuah error underflow aritmatik (misalnya, hasil
dari operasi aritmatik lebih kecil dari nilai terkecil yang dapat disimpan di
dalam komputer).
Gambar
8.10 Beberapa kelas eksepsi pustaka standard
Kesimpulan
Dalam C++, pembagian oleh nol
menggunakan aritmatika integer secara umum menyebabkan sebuah program berhenti
secara prematur. Dalam aritmatika pecahan, beberapa implementasi C++
mengijinkan pembagian oleh nol, dimana pada kasus itu akan dihasilkan infinitas
negatif atau positif yang ditampilkan sebagai INF atau –INF.
Blok try dimaksudkan untuk menangani eksepsi. Blok try mengapit statemen-statemen yang dapat menyebabkan eksepsi dan
statemen-statemen yang harus dilompati jika eksepsi terjadi.
Sedikitnya terdapat satu handler catch yang harus disediakan mengikuti
setiap blok try. Parameter eksepsi
dideklarasikan sebagai sebuah referensi yang menunjuk ke tipe eksepsi yang
dapat diproses oleh handler catch.
Ketika eksepsi terjadi di dalam blok try,
handler catch yang dieksekusi adalah
yang memiliki tipe yang cocok dengan tipe eksepsi yang terjadi. Jika paramate
eksepsi menyertakan sebuah nama parameter opsional, maka handler catch dapat menggunakan nama parameter
itu untuk berinteraksi dengan eksepsi yang ditangkap di dalam tubuh handler catch, yang dibatasi oleh kurung kurawal
({ dan }). Handler catch secara umum
melaporkan error kepada pengguna, menyimpannya di dalam sebuah file, dan
memberhentikan program secara normal atau mencoba melakukan strategi alternatif
dalam melakukan tugas yang tadinya sudah gagal.
Adalah hal yang mungkin bahwa
suatu handler eksepsi, setelah menerima sebuah eksepsi, untuk memutuskan bahwa
ia tidak bisa memproses eksepsi tersebut atau ia hanya dapat memproses eksepsi
itu secara parsial. Pada kasus semacam itu, handler eksepsi dapat mengalihkan
penanganan eksepsi itu kepada handler eksepsi lain.
Ketika sebuah eksepsi dilempar
tetapi tidak ditangkap di dalam skop tertentu, tumpukan pemanggilan fungsi “terurai” dan percobaan untuk menangkap
eksepsi dilakukan di blok try...catch
sebelah luar. Penguraian tumpukan pemanggilan fungsi berarti bahwa fungsi yang
di dalamnya eksepsi tidak ditangkap berhenti, semua variabel lokal dihancurkan,
dan kendali program kembali ke statemen yang semula memanggil fungsi tersebut.
Jika sebuah blok try mengapit
statemen tersebut, maka penumpukan pemanggilan terjadi lagi. Jika tidak
terdapat handler catch yang menangkap
eksepsi ini, maka fungsi terminate
dipanggil untuk menghentikan program.
Standard C++ menspesifikasi bahwa,
ketika operator new gagal, ia akan
melempar sebuah eksepsi bad_alloc
(didefinisikan di dalam header <new>).
Praktek pemrograman yang umum
dilakukan adalah mengalokasikan memori dinamis, menugaskan alamat memori itu
kepada sebuah pointer, menggunakan pointer untuk memanipulasi memori, dan
membebaskan memori dengan delete
ketika memori itu tidak lagi dibutuhkan. Jika sebuah eksepsi terjadi setelah
alokasi memori berhasil dilakukan tetapi sebelum statemen delete dieksekusi, maka akan terjadi kebocoran memori. Standard C++
menyediakan template kelas unique_ptr
di dalam header <memory> untuk
menangani situasi semacam ini.
Latihan
1)
Tulislah sebuah program yang
mengilustrasikan bahwa semua destruktor untuk objek yang dikonstruksi di dalam
suatu blok dipanggil sebelum sebuah eksepsi dilemparkan dari blok tersebut.
2)
Tulislah sebuah program yang
mengilustrasikan bahwa destruktor objek anggota hanya dipanggil untuk
objek-objek anggota yang dikonstruksi sebelum terjadinya eksepsi.
3)
Tulislah sebuah program yang
mendemonstrasikan beberapa tipe eksepsi yang ditangkap menggunakan handler
eksepsi catch(...).
4)
Tulislah sebuah program yang
mengilustrasikan pelemparan-ulang sebuah eksepsi.
5)
Tulislah sebuah program yang
menunjukkan bahwa urutan handler eksepsi itu penting.
No comments:
Post a Comment