Kamis, 02 Januari 2014

Bekerja dengan banyak file cpp...

Bekerja dengan banyak file .cpp (multiple source code)

Bagaimana cara terbaik untuk membuat program yang besar, program yang kecil memudahkan kita untuk memahami cara kerjanya, alurnya, object-object didalamnya, dan lain-lain, sedangkan program yang besar akan lebih sulit untuk memahaminya. Oleh karena itu untuk membuat program yang besar diperlukan teknik agar memudahkan struktur program tersebut dan agar program tersebut mudah di pahami.

1.    Memecah Program menjadi bagian-bagian kecil
Dengan berkembangnya program kita menjadi besar, kita tentu tidak ingin keseluruhan program tersebut ditulis didalam satu file source code, hal itu Akan menyulitkan ketika ada perubahan didalam source kode. Ketika program kita sudah mencapai beberapa ribu baris, sudah selayaknya kita harus memecah program tersebut menjadi beberapa source code.

Pecahan source code tersebut dapat berarti setiap bagian file akan memiliki fungsi-fungsinya masing-masing sesuai peruntukkannya dan dapat saling dihubungkan satu sama lainnya. Hubungan diantara source code yang terpisah tersebut dilakukan hanya melalui file header dari masing-masing source code tersebut. Ini berarti setiap source code pada dasarnya adalah object yang berdiri sendiri dan siap untuk dieksekusi oleh object lainnya, hanya saja untuk mengeksekusi object yang berbeda diperlukan interface mengenai apa saja fungsi-fungsi yang tersedia didalam masing-masing object tersebut, interface tersebut adalah file header dari masing-masing object.

1.1.    Memahami proses Build C++
Sebelum kita memecah source code, ada baiknya kita memahami bagaimana dasar kompilasi didalam C++. Kenyataanya, compiling tidak menghasilkan file executable. Untuk menghasilkan file executable merupakan suatu proses yang bertingkat, tingkat yang penting adalah preprocessing, compilation, dan linking. Secara keseluruhan proses menghasilkan file executable dari source code akan dikatakan sebagai proses build.

1.1.1.    Preprocessing
Langkah awal dari proses build adalah saat compiler menjalankan C preprocessor. Yang berfungsi untuk melakukan perubahan textual terhadap file sebelum langkah kompilasi selanjutnya. Preprocessor ini mengenal tanda-tanda preprocessor directives didalam source code tanda tersebut adalah symbol pound/pagar (#). Dilain pihak compiler sendiri tidak mengenali tanda directives tersebut. Contoh statementnya seperti ini:

#include <iostream>

Perintah diatas akan menyuruh preprocessor untuk mencuplik isi dari file iostream.h dan seolah-olah menempelkannya secara langsung pada posisi #include <iostream> didalam file. Dan selanjutnya compiler akan dapat membaca baris kode tempelan ini.

Preprocessor juga berguna untuk menempelkan macro. Macro adalah string berbentuk text yang di-replace oleh preprocessor. Dengan macro kita dapat membuat banyak constanta didalam satu titik central saja, sehingga akan memudahkan kita untuk mengubah-ubah nilai constanta-constanta tersebut. Contohnya:

#define MY_NAME "Kuda Liar"

Kita cukup menggunakan MY_NAME saja ketimbang menggunakan "Kuda Liar" didalam file source code. Misalnya:

cout << "Hello " << MY_NAME << '\n';

dalam statement diatas, compiler akan melihatnya sebagai:

cout << "Hello " << "Alex" << '\n';

contoh lainnya:

#define VERSION 4

// ...

cout << "The version is " << VERSION

Nah, disebabkan oleh karena preprocessor bekerja sebelum compiler memproses source code, ia juga dapat digunakan untuk melakukan penghapusan baris statement didalam source code. Ini berguna saat kita misalnya perlu mengkompilasi beberapa baris code saja saat melakukan build dalam mode debug. Kita dapat melakukan ini dengan menyatakan kepada preprocessor untuk men-sisipkan baris kode jika macro telah terdefinisi. Contohnya:

#include <iostream>

#define DEBUG             //deklarasi macro 

     using namespace std;

int main ()

{

    int x;

    int y;

    cout << "Enter value for x: ";

    cin >> x;

    cout << "Enter value for y: ";

    cin >> y;

    x *= y;

#ifdef DEBUG        //jika kondisi kompilasi = DEBUG = true maka perintah cout disembunyikan

    cout << "Variable x: " << x << '\n' << "Variable y: " << y;

#endif

//

}

Kita akan bahas lebih lanjut mengenai macro saat kita membahas penggunaan multiple header file.

1.1.2.    Compilation
Compilasi bertujuan merubah source code (file .cpp) menjadi suatu object file (.o atau .obj). Suatu object file berisikan program kita dalam bentuk yang dapat dipahami oleh processor computer – machine language instruction -  untuk setiap fungsi didalam source code kita. Jadi satu file .cpp akan menghasilkan satu file object, nah jika didalam suatu project program terdapat lebih dari satu file .cpp, maka compilasi nya juga akan menghasilkan lebih dari satu file obj.

1.1.3.    Linking
Hasil dari compiler – obj file belum dapat dieksekusi oleh pengguna, tujuan dari melakukan linking adalah membuat satu file yang bisa dieksekusi lepas dari object file dan library-nya.
Linker akan membuat satu file dalam bentuk yang dapat dieksekusi dan men-transfer content dari individual object file menjadi bisa dieksekusi (.exe / .dll). Linker juga mem-perantarai suatu object file yang mempunyai / memanggil fungsi-fungsi yang definisinya me-refferensi dari object lainnya diluar object originalnya. Contohnya: untuk setiap fungsi yang merupakan bagian dari C++ Standar Library, maka berarti kita sedang memanggil fungsi yang tidak terdefinisi didalam source code kita, melainkan fungsi tersebut ada di pustaka C++ yang kita #include (ingat bagian preprocessor) kan file headernya kedalam source code kita. Pustaka standar C++ itu sendiri adalah object file yang sudah jadi dan tersedia dalam bentuk file .dll contohnya libgcc_s_dw2-1.dll.
Ini menunjukkan bahwa jika kita membuat lebih dari satu file .cpp, maka jika source code kita memanggil fungsi-fungsi yang terdapat pada file .cpp lain akan dibutuhkan definisi fungsi tersebut dalam bentuk file header .hpp / .h, dan kelak saat kompilasi setiap file .cpp akan menjadi file .o / .obj sendiri-sendiri. Kesemua file .o / obj tersebut dapat disatukan menjadi satu object tunggal oleh linker.

2.    Mengapa kita butuh kompilasi dan linking yang terpisah-pisah?
Dikarenakan tidak setiap fungsi yang ada harus didefinisikan dalam satu file object, adalah memungkinkan untuk melakukan kompilasi terhadap satu file source code, dan kelak dapat dilakukan link menjadi satu. Jika kita merubah satu file .cpp, tapi tidak merubah file .cpp yang lainnya maka compilasi cukup dilakukan terhadap file .cpp yang dirubah saja, sedang lainnya tidak perlu.

2.1.    Bagaimana memecah program menjadi banyak file
Jadi bagaimana kita men-strukturkan kode kita kedalam kompilasi yang terpisah-pisah? Mari kita pelajari contoh sederhana tentang satu program, asli.cpp, yang mana kita ingin menggunakannya didalam program yang lainnya.
Langkah 1: Pisahkan deklarasi dan definsi (Splitting our declarations and definitions)
Berikut ini contoh source code asli.cpp sebelum dipisahkan.

//===== asli.cpp

#include <iostream>

using namespace std;

struct Node

{

    Node *p_next;

    int value;

};



Node* addNode (Node* p_list, int value)

{

    Node *p_new_node = new Node;

    p_new_node->value = value;

    p_new_node->p_next = p_list;

    return p_new_node;

}

void printList (const Node* p_list)

{

    const Node* p_cur_node = p_list;

    while ( p_cur_node != NULL )

    {

        cout << p_cur_node->value << endl;

        p_cur_node = p_cur_node->p_next;

    }

}

int main ()

{

    Node *p_list = NULL;

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

    {

        int value;

        cout << "Enter value for list node: ";

        cin >> value;

        p_list = addNode( p_list, value );

    }

    printList( p_list );

}

mari kita perhatikan bagian Node* dan printList yang merupakan fungsi dalam source code asli.cpp, Node* dideklarasikan dengan:

Node* addNode (Node* p_list, int value)


Sedangkan definisi Node* adalah:
{

    Node *p_new_node = new Node;

    p_new_node->value = value;

    p_new_node->p_next = p_list;

    return p_new_node;

}

Dan PrintList dideklarasikan dengan:
void printList (const Node* p_list)

Sedangkan definisi printList adalah:
{

    const Node* p_cur_node = p_list;

    while ( p_cur_node != NULL )

    {

        cout << p_cur_node->value << endl;

        p_cur_node = p_cur_node->p_next;

    }

}

Dengan demikian maka yang merupakan bagian deklarasi adalah:

struct Node {Node *p_next; int value;};
Node* addNode (Node* p_list, int value);
void printList (const Node* p_list);

Mari kita letakkan bagian deklarasi tersebut dalam file extobj.h

//========== extobj.h
struct Node {Node *p_next; int value;};
Node* addNode (Node* p_list, int value);
void printList (const Node* p_list);

Sedangkan baik deklarasi dan definisi dari kedua fungsi tersebut kita letakkan didalam file extobj.cpp

//======== extobj.cpp
#include <iostream>
#include "extobj.h"
using namespace std;
Node* addNode (Node* p_list, int value)
{
    Node *p_new_node = new Node;
    p_new_node->value = value;
    p_new_node->p_next = p_list;
    return p_new_node;
}
void printList (const Node* p_list)
{
    const Node* p_cur_node = p_list;
    while ( p_cur_node != NULL )
    {
        cout << p_cur_node->value << endl;
        p_cur_node = p_cur_node->p_next;
    }
}

Nah dengan adanya kedua file tersebut file extobj.h dan extobj.cpp, maka file asli.cpp cukup melakukan directive #include saja kepada file extobj.h dan selanjutnya bagian int main() dapat menggunakan fungsi-fungsi yang ada didalam file extobj.cpp.

//=========asli.cpp
#include <iostream>
#include "extobj.h"
using namespace std;
int main ()
{
    Node *p_list = NULL;
    for ( int i = 0; i < 10; ++i )
    {
        int value;
        cout << "Enter value for list node: ";
        cin >> value;
        p_list = addNode( p_list, value );
    }
    printList( p_list );

}

Tidak ada komentar:

Posting Komentar