Pendahuluan Prolan dan bahasa C.zip

36 

Teks penuh

(1)

Pengenalan Singkat C++

J.Linggarjati

May 7, 2007

Pada saat training fullcast minggu ke 11 dan 12, topik yang tertera di silabus adalah ”Introduction to C++” dan ”OOP in C++”. Melihat kembali ke buku absensi fullcast, topik ini telah dibahas pada pertemuan ke 53 hingga pertemuan ke 60. Dilihat dari Judul dan Rincian Materi, maka berikut ini apa yang terjadi di pertemuan ke-53 hingga 60.

1. OOP and Review: Review profiling and memory leak; OOP: Encapsu-lation and Inheritance

2. Exercise Encapsulation and pointer

3. OOP-Mini Project: Review and discussion, mini project (pertemuan 55 hingga 58).

4. Polymorphysim, OOP Project 2

5. OOP Project 2, Presentation

Projek yang dibuat adalah database mini; projek ini sudah dilakukan pada minggu-minggu sebelumnya (hanya saja dalam bahasa C). Maka projek database mini tersebut dikonversi ke bahasa C++.

Tulisan kecil ini mencoba untuk menangkap beberapa hal tentang C++, hal ini mengingat peserta fullcast terdiri dari dua jurusan, Teknik Informatika dan Sistem Komputer. Berdasarkan silabus matakuliah, Sistem Komputer belum pernah mendapat mata-kuliah C++, hal ini tentu menjadi kendala, tetapi tidaklah terlalu besar, mengingat C sudah mereka kuasai dengan baik. Baiklah, tulisan ini bukan pekerjaan pribadi, tetapi dari terjemahan bahasa inggris yang berjudul ”A Quick Introduction to C++ - Tom Anderson”. Secara umum, tulisan ini sangat baik, karena si penulis asli sudah mengajar C++ di kelas maupun di projek selama 15 tahun. Jadi pengalaman dia sungguh berharga bagi mahasiswa untuk dibaca dan dimengerti.

(2)

1

Pengenalan

Artikel ini memperkenalkan bahasa C++ secara sederhana, dari sisi konsep-tual dan point-point penting dari subset C++ yang lebih mudah dimengerti daripada mempelajari seluruh topik-topik pada C++. Tulisan ini dibuat oleh Tom Anderson, sebagai bahan mengajar di mata-kuliah Sistem Op-erasi - Projek Nachos (Not Another Completely Heuristic Operating Sys-tem). Meskipun orientasi tulisan ini pada Sistem Operasi, namun ia mem-buat contoh-contoh program yang mampu memberikan gambaran bagi pem-ula C++, tentang dasar dari bahasa C++.

Dia memberikan gambaran bahwa dasar-dasar C++ dapat dimengerti dan dicoba dalam hitungan hari. Hal mendasar yang ia tekankan adalah bahwa C++ sebagai OOP (Object Oriented Programming) adalah teknik yang berguna untuk men-sederhanakan program. Meskipun pada kenyataan-nya, C++ adalah bahasa yang kompleks dengan fitur-fitur yang sangat sulit untuk dicari manfaatnya.

Maka kemudian ia memberikan batasan apa yang perlu, agak perlu, dan tidak perlu bagi pemula C++:

1. Konsep dasar yang harus dimengerti, yaitu classes, member functions, constructors, destructors - hal-hal ini harus di ketahui dan dimengerti cara penggunaannya.

2. Hal-hal yang terkadang berguna, yaitu single inheritance dan templates - hal-hal ini harus diketahui oleh pemula C++, tapi dapat dihindari pada prakteknya, paling tidak hingga mendapatkan cukup pengalaman di C++.

3. Fitur-fitur C++ yang bukanlah ide bagus, dan justru harus dihindari, seperti multiple inheritance, exceptions, overloading, references, dan lain-lain.

(3)

menyita banyak waktu, tetapi Anda akn tahu dari pengalaman kapan su-atu fitur berguna dan kapan ia tidak. Akhirnya Anda akan dapat membuat keputusan untuk melakukan hanya satu implementasi.

Teknik yang baik dalam mempelajari sebuah bahasa adalah membaca projek program yang jelas untuk dicerna pada bahasa tersebut. Penulis ini membuah projek Nachos sebagai contoh program yang ia harapkan dapat mudah dibaca; Nachos ditulis dalam subset C++, seperti yang akan dije-laskan pada bagian berikutnya dari tulisan ini.

2

C dalam C++

Secara garis besar, C++ adalah superset dari C, atau C adalah subset dari C++. Dan program ANSI C yang ditulis dengan standard dapat dikompilasi dengan kompiler C++. Namun ada beberapa perbedaan mendasar, sebagai berikut:

1. Seluruh fungsi harus dideklarasikan terlebih dahulu sebelum mereka digunakan, tidak seperti pada C yang di-defaultkan ke tipe int.

2. Seluruh deklarasi fungsi dan definisi headers harus menggunakan gaya dekrasi baru, sebagai contoh:

extern int foo(int a, char* b);

Sedangkan, extern int foo(); berarti fungsi foo tidak memiliki argu-ments. Bahkan, beberapa saran mengatakan untuk menggunakan kom-piler C++, walau untuk koding C sjaja, karena ia akan menangkap error, seperti misused functions (fungsi yang salah penggunaannya).

3. Jika Anda perlu untuk me-link file objek C dengan C++, maka Anda perlu mendeklarasi fungsi C untuk C++, sebagai berikut:

extern "C" int foo(int a, char* b);

Jika tidak maka kompiler C++ akan mengubah nama fungsi foo dalam bentuk yang aneh.

(4)

3

Konsep-konsep Dasar

Sebelum memberikan contoh-contoh program C++, mari kita lihat beberapa konsep dasar bahasa OOP. Jika penjelasan awal ini terkesan sulit dicerna, maka selanjutnya akan ada beberapa contoh program untuk memperjelas.

1. Classes dan objek. Sebuah class sama dengan struc pada C, hanya saja semua definisi struktur data dan semua fungsi yang beroperasi terhadap struktur data tersebut di gabung menjadi satu kesatuan yang disebut dengan class. Objek adalah instan dari sebuah class; beberapa objek berbagi fungsi yang sama dengan objek-objek lainnya dari satu class yang sama, namun setiap objek (setiap instan) memiliki struktur data terpisah. Kesimpulan sebuah class, mendefinisikan 2 aspek dari objek: data yang dimiliki dan sifat yang dipunyai.

2. Member functions. Sifat dari suatu class, diimplementasikan dalam fungsi, yang disebut member-functions. Terkadang di buku-buku C++, hal ini disebut juga dengan method dari suatu class. Selain member functions, sifat class juga ditentukan oleh:

(a) Apa yang Anda lakukan ketika objek baru dibuat (fungsinya diberi-nama constructor) - dengan kata lain melakukan inisialisasi data objek.

(b) Apa yang Anda lakukan ketika objek dihapus (fungsinya diberi-nama destructor)

3. Private versus public members. Public member dari sebuah class adalah sesuatu yang dapat dibaca atau ditulis oleh siapa saja, berkaitan den-gan data member, atau dipanggil oleh siapa saja, berkaitan denden-gan member function. Sedangakan sebuah private member dapat hanya dibaca, ditulis atau dipanggil oleh sebuah member function pada class itu sendiri.

(5)

3.1

Classes

C++ classes pada intinya serupa dengan struct pada C. Malah, pada C++, struct dianggap sebagai class yang hanya memiliki data member public. Pada penjelasan kedepan tentang bagaimana classes bekerja, kita akan menggu-nakan sebuah stack class sebagai contoh.

1. Member functions. Berikut ini adalah sebuah contoh dari satu class dengan sebuah member function dan beberapa data members:

class Stack { public:

// Push an integer, checking for overflow. void Push(int value);

//Index of the top of the stack. int top;

// The elemetns of the stack. int stack[10];

};

void Stack::Push(int value) {

ASSERT(top < 10); // stack should never overflow stack[top++] = value;

}

Class Stack memiliki dua data members, top dan stack, dan satu mem-ber function, Push. Notasi class::function menyatakan fungsi yang merupakan anggota dari class tersebut (aturan dalam menulis nama fungsi adalah dengan huruf besar diawal suku kata). Isi dari fungsi itu sendiri didefinisikan dibawahnya.

Perhatikan bahwa kita menggunakan fungsi ASSERT untuk menge-cek apakah stack telah penuh; ASSERT akan memberitahu debugger apabila kondisi-nya adalah false. Sangat baik untuk menggunakan AS-SERT sepanjang baris pada program yang Anda buat. Akan lebih baik jika mampu mendeteksi kesalahan program dengan memanfaatkan fungsi ASSERT, hal ini dapat menghindari hal yang lebih fatal, misal-nya program menulis data pada lokasi acak di memori.

(6)

Jika kita memiliki sebuah pointer, katakan s, yang menunjuk ke sebuah objek Stack, maka kita dapat mengakses variabel top dengan cara,

s->top

sama seperti di C. Namun di C++, kita juga dapat memanggil member fungsi dengan sintaks:

s->Push(17);

Tentu saja, seperti di C, pointer s harus menunjuk ke objek stack yang sesungguhnya.

Didalam member function, kita dapat mengacu ke member dari class tersebut hanya dengan nama-nya saja. Atau dengan kata lain, defin-isi class telah membuat ruang-lingkup yang menyertakan defindefin-isi dari member (yaitu fungsi dan data).

Perhatikan juga bahwa jika Anda berada di dalam sebuah member func-tion, maka Anda mendapatkan sebuah pointer yang menunjuk ke objek yang memanggil, dengan cara menggunakan variabel this. Namun, jika Anda ingin memanggil member function lain pada objek yang sama, maka Anda tidak memerlukan pointer this ini. Mari kita memperluas contoh Stack untuk mengilustrasikan hal ini, dengan menambahkan fungsi Full().

class Stack { public:

// Push an integer, checking for overflow void Push(int value);

bool Full(); int top;

int stack[10]; }

bool Stack::Full() { return (top ==10); }

(7)

void Stack::Push(int value) { ASSERT(!Full());

stack[top++] = value; }

Atau kita dapat juga menulis bagian ASSERT, sebagai berikut:

ASSERT(!(this->Full());

tetapi ingat di dalam sebuah member function, this−> tidak perlu

ditulis (implisit).

Tujuan dari member functions adalah untuk meng-enkapsulasi fung-sionalitas dari suatu tipe objek berikut dengan data yang dimiliki oleh objek tersebut. Ingat bahwa sebuah member function tidak memakan tempat didalam sebuah objek dari suatu class.

2. Private members.

Kita dapat mendeklarasikan sebagian members dari class sebagai pri-vate, yang berarti tersembunyi dari semua, kecuali dari member function-nya sendiri; dan sebagian members-function-nya lagi sebagai public, yang be-rarti dapat dilihat dan diakses oleh semua. Baik data member maupun member functions dapat menjadi public atau private.

Pada contoh Stack, perhatikan bahwa karena kita telah memiliki fungsi Full(), maka kita tidak perlu melihat data top dan data stack[ ] dari luar class tersebut - bahkan, kita menginginkan bahwa users dari class Stack tidak perlu tahu bagaimana implementasi internal dilakukan, ini penting, apabila kita akan mengubah implementasi iternalnya. Dengan demikian kita menulis-ulang class Stack, sebagai berikut:

class Stack { public:

// Push an integer, checking for overflow. void Push(int value);

//Returns TRUE if the stack is full, FALSE otherwise. bool Full();

private:

int top; // Index of the top of the stack.

(8)

Perhatikan, sebelumnya, dengan diberikan sebuah pointer ke objek Stack, katakan pointer s, semua bagian dari program dapat mengak-ses s−>top; dan tentunya hal ini tidak baik dari sisi pemrograman.

Sekarang, dengan dibuatnya top sebagai data member private, maka hanya member fungsi Full() yang dapat mengakses top. Jika bagian lain dari keseluruhan program mencoba mengakses ke s−>top, maka

kompiler C++ akan melaporkan terjadinya error.

Anda dapat menentukan bagian dari suatu class private: atau public:. Jika tidak dispesifikasikan, maka class members akan private, maka contoh class Stack dapat ditulis, sebagai berikut:

class Stack { int top;

int stack[10]; public:

void Push(int value); bool Full();

};

Bentuk apapun yang Anda sukai hanyalah masalah gaya pemrograman, namun sebaiknya dibuat eksplisit, sehingga terlihat jelas apa yang di-maksud. Pada projek Nachos, kita membuat semuanya eksplisit. Apa yang bukan hanya masalah gaya pemrograman adalah semua data member dari sebuah class sebaiknya private. Semua operasi pada data seharusnya melalui class member functions. Membuat data private, menambah sifat modularitas dari keseluruhan sistem, karena Anda dapat merevisi bagaiman data member disimpan, tanpa mengubah bagaimana Anda mengakses data tersebut.

3. Constructors dan operator new.

Pada C, untuk membuat sebuah objek baru dari tipe Stack, seseorang dapat menulisnya, sebagai berikut:

struct Stack *s = (struct Stack *) malloc(sizeof (struct Stack)); InitStack(s, 17);

Fungsi InitStack() dapat memiliki argument kedua sebagai ukuran dari stack yang akan dibuat dan menggunakan malloc() untuk mendapatkan array dari 17 integers.

(9)

Stack *s = new Stack(17);

Fungsi new menggantikan fungsi malloc(). Sedangkan untuk menen-tukan bagaimana suatu objek diinitialisasi, seseorang dapat mendeklarasi sebuah fungsi constructor, sebagai member dari class tersebut, dimana nama fungsi constructor dibuat sama dengan nama classnya:

class Stack { public:

Stack(int sz); // Constructor: initialize variables, allocate space.

void Push(int value); // Push an integer, checking for overflow.

bool Full(); // Returns TRUE if the stack is full, FALSE otherwise.

private: int size; int top; int *stack; };

Stack::Stack(int sz) { size = sz;

top = 0;

stack = new int[size]; // Let’s get an array of integers.

}

Terdapat beberapa hal baru pada contoh koding tersebut, kita coba lihat satu persatu.

Operator new secara otomatis membuat (yaitu mengalokasikan) jek tersebut dan kemudian memanggil fungsi constructor untuk ob-jek yang baru dibuat ini. Hal yang sama terjadi jika, misalnya Anda mendeklarasikan sebuah objek sebagai variabel otomatis (variabel bi-asa) didalam sebuah fungsi atau blok - maka kompiler akan menga-lokasikan ruang untuk objek tersebut didalam bagian stack dan me-manggil contructor didalam objek tersebut.

Dalam contoh ini, kita membuat dua (2) buah stack dengan ukuran yang berbeda-beda; satu mendeklarasikan objek sebagai suatu variabel otomatis, dan satunya lagi dengan perintah new (variabel pointer).

void test() { Stack s1(17);

(10)

Perhatikan, terdapat dua cara untuk menyediakan argument bagi con-structors: pertama dengan new, Anda menaruh daftar argument sete-lah nama class, dan kedua dengan variabel automatis (biasa atau global variabel), Anda menaruhnya setelah nama variabel.

Sangat penting untuk selalu mendefinisikan constructor pada setiap class yang dibuat, dan juga penting untuk memastikan bahwa con-structor menginisialisasi seitap data member dari suatu class. Jika Anda tidak mendefinisikan constructor, maka kompiler akan secara au-tomatis mendefinisikan satu constructor untuk Anda, dan percayalah, constructor tersebut tidak akan melakukan apa yang Anda harapkan. Data members akan diinitialisasi secara random, nilai yang berbeda-beda, dan pada saat program Anda berjalan sekarang, mungkin akan tidak berjalan pada lain waktu Anda mengkompilasi ulang.

Sama dengan variabel C biasa, variabel-variabel yang dideklarasikan didalam sebuah fungsi akan didealokasikan secara otomatis pada saat fungsi tersebut selesai; sebagai contoh, objek s1 dialokasi ketika fungsi test selesai. Data yang teralokasi dengan new (seperti pada objek s2) akan tersimpan pada heap, dan akan tetap berada disana walaupun fungsi telah selesai; data pada heap harus dibuang secara eksplisit den-gan menggunakan perintah delete.

Operator new dapat pula digunakan untuk mengalokasikan arrays, ter-ilustrasi berikut ini untuk array of ints dimensi size:

stack = new int[size];

Perhatikan bahwa Anda dapat menggunakan new dan delete (akan di-jelaskan pada bagian berikutnya) dengan tipe-tipe build-in, seperti int dan char, serta dengan objek-objek class seperti Stack.

4. Destructors dan operator delete.

Sama halnya dengan new sepagai pengganti malloc(), maka pengganti free() adalah delete. Untuk mengahup objek Stack yang kita alokasikan dengan new, kita dapat melakukan:

delete s2;

(11)

class Stack { public:

Stack(int sz); ~Stack();

void Push(int value); boot Full();

private: int size; int top; int *stack; };

Stack::~Stack() { delete [] stack; }

Destructor memiliki tugas untuk mendealokasikan data yang telah ter-alokasi oleh contructor. Banyak class yang tidak memerlukan destruc-tor, dan sebagian pemrogram akan menggunakan destructor untuk menutup files.

(12)

tanpa Anda harus melakukan apa-apa (termasuk tanpa memanggil per-intah delete).

Pada projek Nachos, kita selalu menggunakan teknik eksplisit untuk men-galokasikan dan meng-dealokasikan objek dengan new dan delete, agar jelas waktu constructor dan destructor dipanggil. Sebagai contoh, jika sebuah ob-jek berisi obob-jek lainnya sebagai member variable, maka kita menggunakan new untuk secara eksplisit mengalokasikan dan menginialisasi member vari-abel, daripada secara implisit mengalokasikannya sebagai bagian dari ob-jek tersebut. C++ memiliki aturan yang kurang jelas untuk urutan dalam constructor - destructor dipanggil, pada saat Anda secara implisit menga-lokasikan dan mendeamenga-lokasikan objek-objek. Pada prakteknya, meskipun sederhana, alokasi secara eksplisit membuat program lebih lambaat dan mem-buat Anda dapat lupa untuk men-dealokasikan sebuah objek (hal yang se-harusnya dihindari). Sehingga beberapa programmer tidak setuju dengan cara ini.

Ketika Anda men-dealokasikan sebuah array, Anda harus memeberiatahu kompiler bahwa Anda sedang men-dealokasikan sebuah array, sebagai perbe-daan yang jelas untuk satu element saja pada suatu array. Maka untuk menghapus suatu array integer dalam Stack::∼Stack:

delete [] stack;

3.2

Fitur-fitur dasar lain dari C++

Berikut ini beberapa fitur-fitur dasar C++ yang berguna untuk diketahui:

1. Ketika Anda mendefinisikan class Stack, maka nama Stack menjadi berguna sebagai tipe data, sama seperti pada saat menggunakan type-def dan enums.

2. Anda dapat mendefinisikan fungsi-fungsi didalam sebuah definisi class, dimana mereka menjadi inline functions, yaitu fungsi yang akan dima-sukan kedalam class itu sendiri, ketika mereka digunakan. Pedomannya adalah gunakan inline functions untuk fungsi yang berisi satu baris dan gunakan sejarang mungkin.

Sebagai contoh, kita dapat membuat fungsi Full() sebagai inline func-tion:

(13)

boot Full() { return (top == size); }; ...

};

Terdapat dua motivasi untuk inline functions: kemudahan dan per-forma program. Jika digunakan terlalu banyak, inline functions dapat membuah koding kita menjadi membingungkan, karena implementasi dari sebuah objek tidak lagi hanya di satu tempat, tapi terpecah dua di file .h dan .c. Inlines functions terkadang dapat mempercepat kod-ing program kita (dengan meniadakan prosedur jump), namun untuk mahasiswa biasanya bukanlah sebagai faktor utama (yang lebih utama adalah membuat program yang sederhana dan tidak ada bug/error). Inline functions dapat juga menyebabkan program lebih lambat, hal ini dapat terjadi karena kode objek dari fungsi tersebut diduplikasi setiap fungsi inline dipanggil, menyebabkan cache banyak terpakai.

3. Didalam fungsi, Anda dapat mendeklarasikan beberapa variabel, mengek-sekusi beberapa perintah, dan kemudian mendeklarasikan lagi beber-apa variabel. Hal ini dbeber-apat membuat koding lebih mudah dibaca. Se-bagai contoh, Anda dapat menulis hal seperti berikut ini:

for (int i = 0; i < 10; i++);

Tergantung pada kompiler Anda, variabel i dapat tetap aktif diluar for, namun terkadang, hal tersebut tidaklah diinginkan.

4. Komentar dapat dimulai dengan / / untuk per baris. Terkadang lebih mudah dari pada /* */ pada C.

5. C++ menyediakan beberapa fitur baru untuk penggunaan const dari ANSI C. Ide dasar dari const adalah memberitahu kompiler bagaimana sebuah variabel atau fungsi digunakan, sehingga kompiler dapat mem-beritahu user jika ada kesalah-gunaan pada variabel maupun fungsi yang bersangkutan. Dengan cara ini, anda dapat menemukan bugs lebih cepat. Lagipula, mana yang lebih cepat? Memperbaiki sebuah error dari kompiler-flagged, atau mencari kesalahan yang sama dengan gdb?

(14)

class Stack { ...

bool Full() const; // Full() never modifies member data

... };

Sama seperti di C, Anda dapat pula menggunakan cosnt untuk mendeklarasikan bahwa sebuah variabel tidak boleh dimodifikasi:

const int InitialHashTableSize = 8;

Hal ini lebih baik daripada menggunakan define untuk konstanta, karena const termasuk type-checked.

6. Input atau output di C++ dapat menggunakan operator>>,<<, dan

objek cin, cout. Sebagai contoh, untuk menulis ke stdout:

cout << "Hello world! This is section " << 3 << "!";

Hal ini sama dengan koding C:

fprintf(stdout, "Hello world! This is section %d!\n", 3);

bedanya, pada C++, contoh koding bersifat type-safe; sedangkan den-gan printf, kompiler tidak akan mengecek apabila Anda mencoba menc-etak angka pecahan sebagai integer. Bahkan, Anda dapat menggu-nakan printf didalam C++, tetapi Anda akan mendapatkan hasil yang aneh jika Anda menggunakan kedua-duanya, printf dan<<pada stream

yang sama. Pembacaan dari stdin sama dengan menulis ke stdout, bedanya stdin menggunakan operator >> sedangkan stdout

menggu-nakan operator <<. Untuk membaca 2 integer dari stdin:

int field1, field2; cin << field1 << field2;

// equivalent to fscanf(stdin, "%d %d", &field1, &field2); // note that field1 and field2 are implicitly modified

(15)

4

Fitur-fitur baru pada C++: Berbahaya

na-mun berguna

Terdapat fitur-fitur baru pada C++, seperti single inheritance dan templates, yang mudah disalah gunakan, namun dapat secara dramatis mempermudah implementasi program jika digunakan dengan baik. Saya akan menjelaskan ide dasar dari fitur-fitur ini, berguna apabila Anda menemukannya dalam program. Anda dapat melewatkan bacaan pada bagian ini - karena bagian ini cukup panjang, kompleks, dan Anda dapat mengerti 99% koding di Nachos tanpa membaca bagian ini.

Hingga detik ini, belum ada perbedaan nyata antara C dan C++. Pada kenyatannya, programer C berpengalaman mengorganisasi fungsi- fungsi mereka dalam modul-modul yang saling terkait kedalam satu struktur data (class). Dan sering menggunakan konvensi penamaan yang mirip dengan C++, seba-gai contoh, penamaan rutin StackFull() dan StackPush(). Namun, fitur yang akan saya diskripsikan memang memerlukan pergeseran paradigma - tidak ada translasi sederhana antara program C dengan program C++. Keuntun-gan yang akan didapat, pada beberapa kondisi, Anda akan dapat menulis koding yang generik, artinya dapat digunakan untuk beberapa macam tipe objek.

Namun, saya menyarankan pemula C++ untuk tidak terlebih dahulu menggunakan fitur-fitur ini, karena bisa salah penggunaannya. Sangat mungkin menulist koding lengkap yang tidak baik dengan inheritance dan templates. Meskipun Anda mungkin berfikir hal tersebut menarik untuk ditulis seba-gai koding, namun pembaca program dapat sulit mengerti koding Anda. Saya yakin pembaca program Anda akan kesulitan dalam membaca program Anda, dan dapat berdampak pada nilai yang diberikan (hal ini untuk ma-hasiswa/i - akademisi). Sedangkan di industri, koding yang sederhana dan mudah dibaca merupakan prioritas utama. Hal yang mudah untuk menulis koding baru, tetapi biaya sesungguhnya data pada saat Anda memastikan bahwa program tersebut tetap berjalan, bahkan pada saat Anda menam-bahkan fitur baru ke program Anda.

(16)

membantu Anda mengidentifikasikan kapan fitur ini dapat dipakai, sehingga Anda dapat kemudian belajar lebih detail tentang fitur-fitur ini!

4.1

Inheritance

Inheritance menangkap kebutuhan bahwa beberapa class dari objek saling berkaitan satu dengan lainnya dalam hal yang berguna. Sebagai contoh, lists dan sorted list memiliki beberapa kesamaan fungsi - mereka sama-sama memperbolehkan user untuk melakukan insert, delete, dan find element yang berada pada list tersebut. Terdapat dua keuntungan dalam penggunaan inheritance:

1. Anda dapat menulis koding generik, yang tidak lagi mementingkan tipe objek apa yang sedang dimanipulasi. Sebagai contoh, Inheritance dipakai secara luas pada sistem window. Semua yang ada di layar (windows, scroll bars, titles, icons) adalah objek yang berdiri sendiri, tetapi mereka semua menggunakan bersama-sama beberapa member functions, seperti rutin Repaint untuk menggambar ulang objek pada layar. Dengan cara ini, koding untuk menggambar ulang seluruh layar dapat secara sederhana memanggil fungsi Repaint pada setiap objek di layar. Koding yang memanggil Repaint tidak perlu untuk menge-tahui tipe objek apa yang berada di layar, sepanjang fungsi tersebut mengimplementasikan kegiatan atau fungsi Repaint.

2. Anda dapat berbagi sebuah implementasi untuk 2 objek. Sebagai con-toh, jika Anda mengimplementasikan lists dan sorted list di C, Anda akan menulis koding tersebut didua tempat yang berbeda - bahkan mungkin, Anda akan menginginkan hanya mengimplementasikan list class. Di C++, Anda dapat mengimplementasikan sorted lists den-gan cara mengden-ganti implementasi insert member function - sedangkan member functions lainnya sama, yaitu delete, isFull, dan print.

4.1.1 Sifat Berbagi - Shared

(17)

Untuk memungkinkan kedua implementasi tetap ada, kita pertama-tama mendefinisikan sebuah abstrak Stack, berisi hanya public member functions, tetapi tanpa data.

class Stack { public:

Stack();

virtual ~Stack();

virtual void Push(int value) = 0’ virtual bool Full() = 0;

};

// For g++, need these even though no data to initialize, Stack::Stack {}

Stack::~Stack() {}

Definisi Stack ini disebut base class atau terkadang superclass. Kita dapat kemudian mendefinisikan 2 class turunan (derived class) yang berbeda, atau bisa juga disebut subclasses, yang mewarisi sifat dari base class. (tentu saja, inheritance bersifat rekursif - satu derived class dapat menjadi base class dari derived class lainnya, dan seterusnya). Perhatikan bahwa saya telah menam-bahkan fungsi pada base class dengan katakunci virtual, untuk menyatakan bahwa mereka dapat didefinisikan ulang oleh setiap class turunannya. Fungsi virtual dinisialisasi dengan 0, menyatakan ke kompiler bahwa fungsi-fungsi tersebut harus didefinisikan oleh derived classes.

Berikut ini cara bagaimana kita dapat mendeklarasikan implementasi basis-array dan basis-list dari Stack. Sintaks : public Stack menandakan bahwa ArrayStack dan ListStack kedua-duanya adalah semacam Stacks, dan berabgi sifat yang sama dengan base class.

Class ArrayStack : public Stack { public:

ArrayStack(int sz); ~ArrayStack();

void Push(int value); bool Full();

(18)

class ListStack : public Stack { public:

ListStack(); ~ListStack();

void Push(int value); bool Full();

private:

List *list; };

ListStack::ListStack() { list = new List; }

ListStack::~ListStack() { delete list;

}

void ListStack::Push(int value) { list->Prepend(value);

}

bool ListStack::Full() { return FALSE;

}

Konsep yang menarik dari ini adalah saya dapat membuat pointers ke instan dari ListStack atau ArrayStack untuk sebuah variabel dari tipe Stack, dan kemudian menggunakannya seperti mereke adalah tipe data dasar.

Stack *s1 = new ListStack; Stack *s2 = new ArrayStack(17);

if (!stack->Full()) s1->Push(5); if (!s2->Full())

s2->Push(6);

(19)

Kompiler akan secara otomatis memanggil operasi ListStack untuk s1, dan operasi ArrayStack untuk s2; hal ini dimungkinkan karena adanya tabel prosedur dari setiap objek; sebenarnya tabel ini semacam mekanisme switch-ing yang dapat memilih implementasi mana yang cocok, dimana objek tu-runan menimpa daftar pointer didalam tabel prosedur yang terdefinisi oleh base class. Mengacu ke koding diatas, program memanggil operasi Full, Push dan delete secara tidak-langsung melalui tabel prosedur, jadi koding program tersebut tidak perlu mengetahui tipe objek-nya masing-masing.

Pada contoh ini, karena saya tidak membuat instan dari abstract class Stack, saya tidak perlu mengimplementasikan fungsi-fungsinya. Hal ini terke-san sedikit aneh, tetapi ingat bahwa derived classes adalah implementasi dari Stack yang spesifik, dan Stack hanya berfungsi untuk mewakili sifat yang sama diantara implementasi yang berbeda. Sebagai jurusan Sistem Kom-puter, lebih mudah jika membayangkan abstract class Stack seperti multi-plexer, satu input banyak output, dimana switching diatur melalui tipe objek masing-masing.

Juga perhatikan bahwa destructor pada Stack adalah virtual function, namun constructor tidak. Hal ini jelas, ketika saya membuat sebuah objek, saya harus tahu tipe objek yang dibuat, apakah tipe ArrayStack atau List-Stack. Kompiler akan memastikan bahwa tidak ada yang membuat instan dari abstract Stack secara tidak sengaja - Anda tidak dapat menginstan-kan class yang memiliki virtual class tanpa implementasi (pure virtual, atau den-gan kata lain, jika salah satu fungsi diset nol didalam definisi class tersebut). Namum, pada saat saya mendealokasikan sebuah objek, saya tidak perlu tahu tipe objek-nya. Pada koding diatas, saya ingin memanggil destruc-tor milik derived objek, meskipun koding program hanya tahu bahwa saya sedang menhapus sebuah objek dari class Stack. Jika destructor tersebut tidak virtual, maka kompiler akan memanggil destructor milik Stack, sedan-gkan hal ini bukanlah yang saya inginkan. Hal ini pula yang merupakan kesalahan program yang mudah dibuat (terjadi pada versi pertama tulisan ini!) - jika Anda tidak mendefinisikan sebuah destructor untuk abstaract class, maka kompiler akan mendefinisikannya satu untuk Anda secara im-plisit (dan itu bukan virtual). Hasil koding diatas tanpa virtual, akan terjadi sebuah memory leak, dan sulit untuk mendeteksi kesalahan seperti ini!

4.1.2 Implementasi Berbagi

(20)

hal ini bukanlah ide yang baik, akan dijelaskan berikutnya mengapa.

Misalnya, saya ingin menambah sebuah member function, NumberPushed(), ke kedua implementasi pada Stack. Class ArrayStack sudah memiliki catatan untuk menghitung jumlah elemen dalam stack, jadi saya dapat mendup-likasikan koding tersebut kedalam Liststack. Secara ideal, saya ingin dapat menggunakan koding yang sama pada dua tempat yang berbeda. Dengan fi-tur inheritance, kita dapat memindah counter tersebut kedalam class Stack, dan kemudian memanggil operasi base class dari derived class untuk mem-perbaharui isi counter tersebut.

class Stack { public:

virtual ~Stack(); // deallocate data

// Push an integer, checking for overflow. virtual void Push(int value);

virtual bool Full() = 0; int NumPushed();

protected:

Stack(); // initialize data private:

int numPushed; };

Stack::Stack() { int numPushed = 0; }

void Stack::Push(int value) { numPushed++;

}

int Stack::NumPushed() { return numPushed; }

Kita dapat memodifikasi baik ArrayStack dan ListStack untuk meman-faatkan sifat baru dari Stack. Saya hanya akan memperlihatkan salah satu dari mereka:

(21)

ArrayStack(int sz); ~ArrayStack();

void Push(int value); bool Full();

private: int size; int *stack; };

ArrayStack::ArrayStack(int sz) : Stack() { size = sz;

stack = new int[size]; }

void ArrayStack::Push(int value) { ASSERT(!FULL());

stack[NumPushed()] = value; Stack::Push();

}

Ada beberapa hal yang perlu diperhatikan:

1. Constructor pada ArrayStack perlu memanggil constructor untuk Stack, agar numPushed dapat terinisialisasi. Hal ini dapat dilakukan dengan cara menambahkan : Stack() pada baris pertama di dalam constructor:

ArrayStack::ArrayStack(int sz) : Stack()

Hal yang sama berlaku untuk destructors. Terdapat beberapa atu-ran khusus yang menentukan urutan pemanggilan antara construc-tor/destructor untuk base class atau construcconstruc-tor/destructor untuk de-rived class. Namun, bergantung pada aturan adalah hal yang tidak baik, dalam hal ini - ketergantung pada dokumentasi untuk menge-tahui apakah koding program bekerja atau tidak!

(22)

tidak menginginkan class lain membuat instan dari Stack ini. Maka, kita membuat Stacks’s constructor sebagai sebuah protected function. Pada kasus ini, hal ini tidaklah terlalu penting, karena kompiler akan error jika orang lain membuat instan dari Stack, karena Stack masih memiliki sebuah pure virtual functions, yaitu Push. Dengan mendefin-isi Stack::Stack sebagai protected, Anda akan aman, walaupun jika seseorang kemudian datang dan mendefinisikan ulang Stack::Push. Perhatikan pula, bahwa saya membuat data member pada Stack pri-vate, bukannya protected. Walaupun terdapat debat soal point ini, se-bagai acuan, Anda seharusnya tidak pernah memperbolehkan sebuah class untuk mengakses data class lain, meskipun antar classes inher-itance. Apabila tidak, jika Anda mengganti implementasi pada base class, maka Anda juga akan mempelajari dan mengganti semua imple-mentasi pada derived classes, tentunya melanggar aturan modularitas.

3. Interface dari derived class secara otomatis termasuk semua fungsi public yang terdefinisi pada base class, tanpa harus secara eksplisit mendaftarkan mereka didalam derived class. Jadi meskipun, kita tidak mendefinisikan NumPushed() didalam ArrayStack, kita tetap dapat memanggil-nya:

ArrayStack *s = new ArrayStack(17);

ASSERT(s->NumPushed() == 0); // sould be initialized to 0

4. Sebaliknya, meskipun kita telah mendefinisikan Stack::Push(), namun ia dideklarasikan sebagai virtual; sehingga jika kita memanggil Push() pada objek ArrayStack, maka kita akan mendapatkan versi Push milik ArrayStack:

Stack *s = new ArrayStack(17);

if (!s->Full()) // ArrayStack::Full s->Push(5); // ArrayStack::Push

(23)

6. Member functions dalam sebuah derived class dapat secara eksplisit memanggil public atau protected functions dalam base class, dengan memanggil nama fungsi tersebut, Base::Function(), seperti contoh berikut ini:

void ArrayStack::Push(int value) {

...

Stack::Push(); // invoke base class to increment numPushed }

Tentunya, jika kita hanya memanggil Push() disini (tanpa menam-bahkan STack::), kompiler akan berfikir kita sedang mengacu ke Ar-rayStack’s Push(), dan hal ini adalah recursif, yang tentunya bukanlah hal yang kita inginkan disini.

Wow! Inheritance didalam C++ menyangkut banyak hal detail. Namun, hal yang kurang dari C++ adalah kecendrungannya untuk membuat im-plementasi file tersebar ke beberapa file; jika Anda memiliki sebuah pohon inheritance yang dalam, akan butuh usaha yang serius untuk mengetahui koding mana yang sebenarnya dieksekusi ketika sebuah member function di-panggil.

Jadi pertanyaan yang perlu dipertanyakan kembali sebelum menggunakan inheritance adalah: apa tujuan Anda? Apakah menulis program dengan jumlah karakter sedikit mungkin? Jika ya, inheritance menjadi berguna, tetapi hal yang sama dapat dilakukan dengan mengubah semua fungsi dan nama variabel menjadi satu karakter saja ”a”, ”b”, ”c” (hanya bercanda). Pointnya adalah, mudah untuk menulis koding yang sukar dibaca apabila menggunakan inheritance.

Jadi kapan waktu yang baik untuk menggunakan inheritance dan kapan ia harus dihindari? Acuan saya adalah hanya menggunakan-nya untuk merep-resentasikan sifat-berbagi antar objek, dan tidak pernah menggunakan-nya untuk menrepresentasikan berbagi-implementasi. Dengan C++, Anda dapat menggunakan inheritance untuk kedua konsep, namun hanya yang pertama yang akan merujuk ke implementasi yang sederhana.

Untuk mengilustrasikan perbedaan antara sifat-berbagi dengan berbagi-implementasi, seumpamanya Anda memiliki banyak macam objek yang Anda butuhkan untuk ditaruh pada lists. Sebagai contoh, hampir semua didalam sebuah sistem operasi menggunakan semacam lists: buffers, threads, users, terminals, dan lain-lain.

(24)

membuat setiap objek turunan dari sebuah base class Object, yang memi-liki pointer maju dan mundur untuk list. Tetapi bagaimana jika beberapa objek perlu pergi ke beberapa lists? Skema pemrograman tersebut akan ga-gal, dan hal ini terjadi karena kita mencoba menggunakan inheritance untuk melakukan berbagi-implementasi (koding untuk pointer maju dan mundur); daripada untuk sifat-berbagi. Teknik yang lebih baik (meskipun sedikit lebih lambat) adalah mendefinisikan implementasi list yang mengalokasikan pointer maju/mundur untuk setiap objek yang terdapat pada sebuah list.

Kesimpulan, jika dua classes berbagi paling tidak beberapa member func-tion - yang mana, memiliki sifat sama, dan jika terdapat koding yang hanya bergantung pada sifat-berbagi, maka terdapat keuntungan dalam menggu-nakan inheritance. Dalam Nachos, locks tidak merupakan turunan dari semaphores, walaupun locks di-implementasikan dengan semaphores. Op-erasi pada semaphores dan locks berbeda. Bahkan, inheritance hanya di-gunakan untuk beberapa jenis lists (sorted, keyed, dan lain-lain), dan untuk beberapa implementasi berbeda dari abstraksi fisik-disk, untuk merefleksikan apakah disk memiliki sebuah track-buffer, dan lain-lain. Sebuah disk digu-nakan dengan cara yang sama, tanpa memperdulikan apakah ia memiliki se-buah track-buffer; perbedaannya hanya terletak pada karakteristik perfoma-nya.

4.2

Templates

Templates adalah konsep yang berguna di C++, namun juga berbahaya. Dengan Templates, Anda dapat melakukan parameterisasi sebuah definisi class dengan katakunci type, untuk memungkinkan Anda menulis koding yang generik dan type-independent. Sebagai contoh, Implementasi Stack kita diatas hanya bekerja untuk pushing dan popping integers; bagaimana jika kita ingin sebuah stack untuk characters, atau floats, atau pointers, atau suatu struktur data tertentu?

Dalam C++, hal ini dengan mudah dapat dilakukan dengan templates:

template <class T> class Stack {

public:

Stack(int sz); ~Stack();

void Push(T value); bool Full();

(25)

int top; T *stack; };

Untuk mendefinisikan sebuah template, kita menambahkan katakunci template pada definisi class tersebut, dan kita menaruh tipe yang di-parameterisasi untuk template tersebut didalam kurung sudut. Jika kita membutuhkan un-tuk parameterisasi implementasi dengan dua atau lebih tipe, maka ia bekerja seperti layaknya sebuah list argument: template<class T, class S>. Kita

da-pat menggunakan type-prameters dimanapun dalam definisi tersebut, persis seperti jika mereka adalah tipe biasa.

Ketiak kita menyediakan implementasi tersebut untuk setiap member functions didalam class, kita juga harus mendeklarasikan mereka sebagai templates, dan sekali lagi, sekali kita melakukan hal tersebut, kita dapat menggunakan tipe parameters tersebut hanya seperti tipe biasa:

// template version of Stack::Stack template <class T>

Stack<T>::Stack(int sz) { size = sz;

top = 0;

stack = new T[size]; //Let’s get an array of type T }

// template version of Stack::Push template <class T>

void Stack<T>::Push(T value) { ASSERT(!Full());

stack[top++] = value; }

Membuat sebuah objek dari sebuah class template sama dengan membuat objek biasa:

void test() {

Stack<int> s1(17);

Stack<char> *s2 = new Stack<char>(23);

(26)

Segala sesuatu berjalan seperti jika kita mendefinisikan dua classes, satu diberinama Stack<int> – sebuah stack untuk integers, dan satu lagi

diberi-nama Stack<char> – sebuah stack untuk characters. s1 bersikap sama

seperti sebuah instan dari yang pertama; s2 bersikap juga sama seperti se-buah instan dari yang kedua. Bahkan, hal ini sama persis dengan bagaimana templates secara tipikal diimplementasikan – Anda memperoleh kopi koding keseluruhan dari template tersebut untuk setiap tipe instant yang berbeda. Pada contoh diatas, kita mendapatkan satu kopi dari koding untuk ints dan satu kopi koding untuk chars.

Jadi apa salahnya menggunakan template? Anda selalu diajarkan untuk membuat koding Anda se-modular mungkin, sehingga ia dapat digunakan kembali, jadi semuanya seharusnya dibuat menjadi template, ya-kan? Salah. Prinsip masalah-nya dengan template adalah mereka dapat membuat de-bugging menjadi sangat sulit - templates mudah digunakan jika mereka ber-jalan baik, tetapi menemukan sebuah bug didalam mereka menjadi sulit. Hal ini sebagian disebabkan karenan generasi C++ debuggers sekarang tidak-lah mengerti benar-benar tentang templates. Namun, tetap lebih mudah men-debug sebuah template daripada dua implementasi yang hampir sama dan hanya berbeda pada tipe data mereka.

Jadi saran terbaik adalah - jangan menggunakan sebuah class didalam sebuah template, kecuali benar-benar terdapat sebuah term dekat, digu-nakan untuk template tersebut. Dan jika Anda benar-benar butuh untuk mengimplementasikan sebuah template, maka implementasikan dan debug sebuah versi non-template terlebih dahulu. Setelah berjalan, maka tidak ter-lalu sulit untuk mengubahnya ke sebuah versi template. Hal yang kemudian Anda harus kuatirkan adalah eksplosi koding - sebagai contoh, besar objek program Anda sekarang menjadi orde megabytes, dikarenakan oleh 15 kopi dari hash tabel/list/... routines, satu untuk setiap tipe yang Anda inginkan ditaruh didalam sebuah hash table/list/... (Ingat, Anda memiliki kompiler yang tidak bersahabat!).

5

Fitur-fitur yang perlu dihindari seperti wabah

Meskipun artikel ini cukup panjang, masih banyak fitur-fitur di C++ yang belum sempat saya bahas. Saya yakin setiap fitur memiliki kepentingannya masing-masing, namun meskipun telah melakukan pemrograman di C dan C++ untuk lebih dari 15 tahun, saya belum pernah menemukan sebuah alasan yang mengena untuk menggunakannya di dalam koding yang saya telah tulis (diluar dari kelas bahasa pemrograman!).

(27)

fitur-fitur ini - mereka mudah disalah-gunakan, sehingga menghasilkan program yang sulit untuk dibaca dan dimengerti; daripada mudah untuk dimengerti. Dalam banyak hal, fitur-fitur ini juga bersifat berulang - terdapat beberapa cara untuk menghasilkan keluaran yang sama. Mengapa harus memiliki dua cara untuk melakukan satu hal yang sama? Mengapa tidak berpegang pada satu saja yang sederhana?

Saya tidak menggunakan fitur-fitur berikut ini pada program Nachos. Jika Anda menggunakannya, caveat hacker (berhati-hati terhadap penggu-naannya).

1. Multiple Inheritance. Suatu hal yang memungkinkan di C++ untuk mendefinisikan sebuah class yang mewarisi sifat dari beberapa classes (sebagai contoh, seekor anjing adalah sebuah binatang dan juga sebuah benda yang berbulu). Jika programs yang menggunakan single inher-itance dapat menjadi sulit untuk dimengerti, maka programs dengan multiple inheritance dapat menjadi membingungkan.

2. References. Variabel Reference secara umum sulit untuk dimengerti; mereka berperan sama seperti pointers, dengan sintaks yang sedikir berbeda (sangat disayangkan, saya tidak sedang bercanda!). Mereka sering digunakan untuk emndeklarasikan beberapa parameters ke se-buah fungsi sebaga reference parameters, seperti pada Pascal. Sese-buah call-by-reference parameter dapat dimodifikasi oleh si fungsi pemang-gil, tanpa si pemanggil harus melemparkan sebuah pointer. Akibatnya adalah parameter tersebut seperti dipanggil secara by value (sehingga tidak dapat diubah), tetapi pada kenyataannya dapat diubah secara tranparan oleh fungsi yang dipanggil. Secara nyata, hal ini dapat men-jadi sumber ketidak-jelasan bugs, belum lagi kenyataan bahwa seman-tik dari references pada C++ secara umum tidaklah jelas.

3. Operator overloading. C++ memperbolehkan Anda mendefinisikan-ulang fungsi dari operators (seperti + dan ¿¿) untuk objek pada class. Hal ini berbahaya (”implementasi mana dari ’+’ yang dimaksud?”), dan ketika digunakan dalam cara yang tidak-berintuisi, menjadi sumber kebingungan yang besar, diperburuk dengan kenyataan bahwa C++ melakukan konversi type secara implisit, yang dapat mempengaruhi op-erator mana yang akan dipanggil. Sayangnya, fasilitas I/O C++ meng-gunakan banyak operator overloading dan references, sehingga Anda

tidak dapat menghindari-nya, tetapi pikir dua-kali, sebelum Anda mendefinisikan-ulang ’+’ menjadi ”operator penggabungan dua strings”.

(28)

argument yang berbeda. Hal ini juga berbahaya (karena hal ini mem-permudah untuk kesalahan terjadi dan mendapat versi fungsi yang tidak diinginkan), dan kita tidak pernah menggunakannya. Kita juga akan menghindari penggunaan defaults arguments (untuk alasan yang sama). Namum perhatikan, bahwa hal ini dapat menjadi ide yang baik, apabila fungsi dengan nama sama terdapat pada beberapa class yang berbeda, dengan syarat mereka menggunakan arguments yang sama dan berfungsi sama - sebuah contoh yang baik dari hal ini adalah metode Print() pada objek Nachos.

5. Standard template library. Sebuah standar ANSI telah muncul seba-gai sebuah rutin librari, yang mengimplementasikan banyak hal seperti lists, hash tables, dan lain-lain; hal ini disebut dengan STL (Stan-dard Template Library). Menggunakan sebuah librari seperti ini se-harusnya membuat pemrograman menjadi sederhana jika struktur data yang Anda butuhkan telah tersedia didalam librari tersebut. Namun, STL mendorong penggunaan legal C++, dan secara kenyataan tidak ada kompiler (termasuk g++) yang dapat mendukung-nya sekarang. Belum lagi bahwa legal C++ menggunakan references, operator over-loading dan function overover-loading.

6. Exceptions. Terdapat dua cara untuk mengembalikan error dari se-buah prosedur. Cara pertama sederhana - cukup dengan mendefin-isikan prosedur tersebut untuk mengembalikan sebuah kode error jika ia tidak dapat melakukan pekerjaannya. Sebagai contoh, rutin librari standar, malloc mengembalikan NULL jika tidak terdapat memori lagi. Namum, banyak programmers malas dan tidak mencek kode error. Jadi apa solusinya? Anda mungkin berfikir untuk menggunakan program-mers yang tidak malas, namun tidak, solusi C++ adalah menambahkan sebuah bahasa pemrograman yang konstruktif! Sebuah prosedur da-pat mengembalikan sebuah error dengan ”membangkitkan sebuah ex-ception” yang akan secara efektif menyebabkan sebuah goto kembali ke execution stack, ke suatu tempat terakhir, dimana seorang programmer menempatkan sebuah exception handler. Anda mungkin berfikir bahwa hal ini sangat aneh untuk menjadi suatu kebenaran, tetap sayangnya, saya tidak membual tentang hal ini.

(29)

1. Aritmatika Pointer. Pointer yang keluar dari lingkup adalah sebuah sumber utama dimana bugs sulit ditemukan pada program C, karena gejala dari hal ini terjadi, dapat mengubah struktur data dalam bagian program yang berbeda-beda. Tergantung pada objek mana yang di-alokasikan pada heap dan juga berpengaruh pada urutannya, pointers bugs dapat muncul dan hilang, secara random. Sebagai contoh, printf terkadaang mengalokasikan memori pada heap, yang dapat mengubah alamat-alamat yang dikembalikan oleh semua calls yang akan datang ke ”new”. Sehingga, menambahkan sebuah printf dapat mengubah hal-hal tertentu, sehingga sebuah pointer yang biasa digunakan (secara kebe-tulan) untuk mengubah sebuah struktur data kritis (misal, pada posisi tengah dari sebuah thread’s execution stack), sekarang dapat menimpa memori yang mungkin tidak akan pernah digunakan.

Hal terbaik menghindari pointers semacam ini adalah dengan sangat berhati-hati menggunakannya. Daripada melalui sebuah array den-gan aritmatika pointer, gunakan variabel index terpisah, dan yakinkan bahwa index tersebut tidak akan pernah lebih besar dari ukuran array tersebut. Kompilers yang teroptimasi telah memiliki kemampuan yang baik, sehingga koding mesin yang dihasilkan akan selalu sama pada kedua kasus ini (dengan aritmatik atau dengan index).

Bahkan, jika Anda tidak menggunakan aritmatika pointer, maka masih mudah terjadi (mudah adalah hal buruk dalam konteks ini!) untuk mendapatkan sebuah error yang dapat menyebabkan program Anda untuk melewati akhir dari sebuah array. Bagaimana Anda memper-baikinya? Definisikan sebuah class yang berisi suatu array dan panjang-nya; sebelum memperbolehkan akses apapun ke array tersebut, Anda dapat kemudian mencek apakah akses tersebut adalah legal atau ter-masuk error.

2. Casts dari integers ke pointer dan sebaliknya. Sumber lain dari pointers yang keluar dari lingkup adalah bahwa C dan C++ memperbolehkan Anda untuk menkonversikan integers ke pointers dan sebaliknya. Tanpa harus dikatakan, menggunakan sebuah nilai integer yang random seba-gai sebuah pointer akan memperbesar kemungkinan terjadinya suatu gejala yang tidak dapat diprediksi dan akan sulit untuk dicari-tahu letak kesalahannya.

(30)

3. Menggunakan bit shift sebagai pengganti dari sebuah operasi perkalian atau pembagian. Hal ini adalah isu yang jelas. Jika Anda sedang melakukan aritmatika, guanakan operator aritmatika; jika Anda sedang melakukan manipulasi bit, gunakan operators bitwise. Jika saya sedang berusaha mengalikan dengan 8, mana yang mudah dimengerti, x ¡¡ 3 atau x * 8? Pada era 70-an, ketika C baru pertama dibuat, x ¡¡ 3 menghasilkan koding mesing yang lebih efesien, tetapi dengan kompiler sekarang, koding mesing yang dihasilkan sama, jadi tingkat kemudahan dalam membaca seharusnya menjadi alasan Anda yang utama.

4. Pernyataan didalam kondisional. Banyak programmers memiliki pen-dapat bahwa penyerdehanaan sama dengan menghemat pengetikan se-banyak mungkin. Hasilnya justru dapat menyembunyikan bugs yang seharusnya mudah untuk dideteksi. Sebagai contoh:

if (x = y) { ...

Apakah maksud sebenarnya x == y? Lagipula, hal seperti ini mudah terjadi secara tidak-sengaja lupa menambahkan tanda sama-dengan. Dengan tidak pernah menggunakan pernyataan didalam sebuah kon-disional, Anda dapat melihat hanya dari pengamatan koding, apakah Anda telah melakukan sebuah kekeliruan.

5. Menggunakan define ketika Anda seharusnya dapat menggunakan enum. Ketika sebuah variabel dapat menyimpan sebuah angka bernilai kecil, maka di C biasanya menggunakan define untuk men-set nama simbo-lik untuk setiap nilai tersebut. Enum dapat melakukan hal ini secara type-safe - ia memungkinkan kompiler untuk menverifikasi bahwa vari-abel tersebut hanya diperuntukkan bagi satu dari keseluruhan nilai yang di-enumerasikan, dan tidak ada yang lain. Lagi, keuntungan-nya adalah untuk meniadakan sebuah kesalahan dari program Anda, sehingga membuat-nya cepat untuk di-debug.

6

Gaya Penulisan Program

(31)

aturan berikut ini (dan beri-tahu kita jika Anda menemukan aturan ini di-langgar oleh kita):

1. Kata-kata dalam sebuah nama dipisahkan, seperti SmallTalk-gaya, (yaitu, huruf besar pada permulaan setiap kata baru). Semua nama class dan nama member function dimulai dengan sebuah huruf besar, ke-cuali untuk member function berbentuk getSomething() dan setSome-thing(), dimana Something adalah sebuah data elemen dari suatu class (yaitu, fungsi accessor). Perhatikan bahwa Anda akan menggunakan fungsi tersebut hanya jika data tersebut dibuat terlihat dari luar, tetapi Anda ingin mengharuskan semua akses menuju ke satu fungsi. Hal ini terkadang merupakan ide yang baik, karena Anda dapat pada waktu berikutnya, menentukan untuk mengolah data tersebut daripada hanya menyimpannya saja.

2. Semua fungsi global seharusnya dibuat huruf-besar, kecuali untuk main dan fungsi librari, yang dibuat huruf-kecil karena alasan kebiasaan pem-rograman terdahulu.

3. Meminimalisasi penggunaan variabel global. Jika Anda menyadari penggunaan dari mereka cukup banyak, maka coba kelompokkan kedalam sebuah class atau lemparkan mereka sebagai arguments ke fungsi yang membutuhkan mereka jika Anda dapat melakukannya.

4. Meminimalisasi penggunaan fungsi global (kebalikan dari member func-tions). Jika Anda menulis sebuah fungsi yang beroperasi pada beber-apa objek, pikirkan kemungkinan membuat-nya sebagai sebuah mem-ber function dari objek tersebut.

5. Untuk setiap class atau satu set dari classes yang berhubungan, buatlah file .h dan .cc yang terpisah. File .h berfungsi sebagai antarmuka untuk class tersebut, sedangkan file .cc berfungsi sebagai implementasi (den-gan syarat file .cc menyertakan file .h dalam inclue). Jika penggunaan suatu file .h tertentu membutuhkan file .h yang lain (sebagai contoh., synch.h membutuhkan definisi class dari thread.h), maka Anda seharus-nya menyertakan hal ketergantungan-seharus-nya didalam file .h, sehingga user dari class Anda tidak perlu mencari-tahu sendiri dependensi-nya. Un-tuk menjaga dari penyertaan-berulang, lingkupi setiap file .h dengan preprocessor semacam ini:

(32)

class Stack { ... }

#endif

Terkadang, hal ini tidaklah cukup, dan Anda dapat memiliki sebuah depedensi-lingkar. Sebagai contoh, Anda dapat memiliki sebuah file .h yang menggunakan sebuah definisi dari sebuah file .h, tetapi yang juga mendefinisikan sesuatu yang dibutuhkan oleh file .h pertama. Dalam hal ini, Anda perlu melakukan sesuatu sebelumnya. Satu hal yang perlu disadari adalah Anda tidak harus selalu mendefinisikan sebuah class secara menyeluruh sebelum ia digunakan. Jika Anda hanya meng-gunakan sebuah pointer ke class Stack dan tidak mengakses member fuctions atau data dari class tersebut, Adna dapat menulis, sebagai pengganti dari menyertakan stack.h:

class Stack;

Ini akan memberi-tahu kompiler tersebut, semua yang ia perlu ketahui untuk berkomunikasi dengan pointer tersebut. Dalam hal-hal tertentu, kondisi ini tidak dapat dilakukan, dan Anda harus memindahkan be-berapa hal kesana-kemari atau mengubah definisi Anda.

6. Gunakan perintah ASSERT sebebasnya untuk mencek bahwa program Anda telah sesuai berkerja. Sebuah assertion adalah sebuah kondisi yang menyatakan bahwa, jika FALSE maka terdapat bug di program; ASSERT men-test sebuah ekspresi dan membatalkannya jika kondisi tersebut salah. Kita menggunakan ASSERT diatas dalam Stack::Push() untuk mencek apakah stack-nya sudah penuh. Ide-nya adalah menangkap error sesegera mungkin, ketika mereka mudah untuk dideteksi, dari-pada menunggu hingga terdapat gejala yang terlihat oleh user, karena error tersebut (seperti, segmentation fault, setelah memori terbuang oleh sebuah pointer tak-terkendali).

(33)

Jika kecepatan adalah faktor utama, ASSERTs dapat didefinisikan un-tuk melakukan cek dalam versi debug pada program Anda, dan di-buang pada versi produksi. Namum banyak orang tetap mengaktifkan ASSERT meskipun dalam program versi produksi.

7. Tulislah sebuah tes-modul untuk setiap modul didalam program Anda. Banyak pemrogram menganggap bahwa mengetes koding berarti men-jalankan program tersebut secara keseluruhan berdasarkan pada be-berapa contoh masukkan; jika ia tidak keluar mendadak (crash), maka artinya program tersebut berjalan dengan baik, benar? Salah. Anda tidak dapat mengetahui secara pasti berapa banyak koding yang di-jalankan untuk tes tersebut. Mari saya ingatkan Anda untuk menjadi lebih metodologikal tentang testing. Sebelum Anda menaruh sebuah modul baru ke dalam sistem yang lebih besar, pastikan bahwa modul tersebut bekerja sesuai dengan yang diinginkan, dengan cara men-tes-nya sendirian. Jika Anda melakukan ini untuk setiap modul, maka ketika Anda menggabungkan modul-modul tersebut, harapan bahwa semuanya akan berjalan baik tidaklah perlu, karena Anda akan menge-tahui bahwa memang program tersebut akan berjalan baik.

Mungkin yang lebih penting adalah, modul tes menyediakan sebuah ke-sempatan untuk mencari bug sebanyak mungkin dengan mengalokasi konteks dalam lingkup yang lebih kecil. Mana yang lebih mudah: mencari sebuah bug dalam 100 baris program, atau dalam 1000 baris program?

7

Kompilasi dan Debugging

Makefiles yang kita berikan, hanya akan berjalan pada versi GNU make, yang disebut ”gmake”. Anda mungkin dapat menaruh ”alias make gmake” didalam file .bashrc Anda.

Anda seharusnya menggunakan gdb untuk men-debug program Anda daripada menggunakan dbx. Dbx tidak dapat mengenal nama-nama dalam C++, jadi Anda akan melihat nama fungsi seperti Run 9SchedulerP6Thread.

(34)

8

Contoh: Sebuah Stack Integers

Kita telah memberi contoh koding lengkap dan berjalan untuk contoh su-atu stack. Anda perlu mempelajari secara menyeluruh koding tersebut dan mengubahnya hingga Anda mengerti fitur-fitur dari C++, seperti yang telah dijelaskan dalam artikel ini.

Untuk mengkompilasi tes sederhana stack, ketik make all - ini akan mengkompilasi tes sederhana stack (stack.cc), turunan tes stack (inherit-stack.cc), dan juga suatu versi template dari stacks (templatestack.cc).

9

Epilogue - Kesimpulan

Saya telah mengutarakan dalam tulisan ini bahwa Anda seharusnya menghin-dari penggunaan beberapa fitur pada C++ dan C. Namun, tentunya Anda akan berfikir, saya pasti melupakan sesuatu - jika seseorang menaruh fitur tersebut dalam suatu bahasa, maka tentunya ada alasan yang baik, bukan? Saya yakin bahwa setiap pemrogram seharusnya mengutamakan penulisan koding yang bersifat mudah dimengerti oleh pembacanya; jika Anda mene-mukan diri Anda sendiri menulis koding, yang sulit dibaca oleh orang lain karena harus melalui sebuah manual agar mengerti, maka Anda hampir di-pastikan terlalu sulit dalam memprogram. Sehingga mungkin terdapat cara lain untuk menulis program Anda secara lebih sederhana dan cara yang lebih jelas untuk mencapai hasil yang sama. Mungkin kodingnya akan sedikit lebih panjang dengan cara ini, namum dalam dunia nyata, program yang berjalan baik dan sederhana agar mudah dimodifikasi orang-lain, yang lebih penting dari pada banyaknya karakter yang Anda harus ketik.

Hal terakhir yang perlu diingat:

”There are two ways of contructing a software design: one way is to make it so simple that there are obviously no deficiencies and the other way is to make it so complicated that there are no obvious deficiencies.” C. A. R. Hoare, ”The emperor’s Old Clothes”, CACM Feb. 1981

10

Projek Database Mini - Sqlite3

(35)

DB

Command

Buddy Appbuddy

Buddy

Appbuddy Buddy

Figure 1: Class-Diagram dbbuddy

Projek database-mini ini menggunakan database-engine yang sudah jadi; yaitu Sqlite3. Database-engine Sqlite3 sendiri, memiliki footprint yang ke-cil sehingga cocok untuk diterapkan pada embedded-linux. Karena Sqlite3 dibuat dalam c, maka ia perlu dibungkus dengan c++, melalui dbbuddy (sebagai database-engine). Kemudian aplikasi yang menggunakan dbbuddy disebut dengan appbuddy. Pada appbuddy, programmer menggunakan li-brary ncurses sebagai tampilan.

Gambar Figure 1 menunjukkan class hierarki yang dibuat oleh tim pro-grammer fullcast.

Dari figure 1, dapat dilihat bahwa terdapat empat class, yaitu Buddy, DB, command, dan Appbuddy. Adapun class command adalah turunan dari class DB, sedangkan terdapat beberapa class lain yang berfungsi sebagai data-member dari class lain, sebagai contoh class Appbuddy berisikan class Buddy dan class command.

Sedangkan untuk appbuddy, diagram class-nya dapat dilihat pada figure 2.

Dari figure 2, dapat dilihat bahwa terdapat tiga class, yaitu dtbuddy t, buddy t, dan AppBuddy. Class buddy t merupakan turunan dari class dt-buddy t, sedangkan class AppBuddy adalah turunan dari class dt-buddy t.

(36)

buddy_t

dtbuddy_t

dtbuddy_t*

dtbuddy_t buddy_t

AppBuddy

buddy_t dtbuddy_t

Figure 2: Class-Diagram appbuddy

Figur

Figure 1: Class-Diagram dbbuddy

Figure 1.

Class Diagram dbbuddy. View in document p.35
Figure 3: Output dari program appbuddy

Figure 3.

Output dari program appbuddy. View in document p.36
Figure 2: Class-Diagram appbuddy

Figure 2.

Class Diagram appbuddy. View in document p.36

Referensi

Memperbarui...

Related subjects :