Friday, December 23, 2016

Bab 8. C++ Untuk Programer



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