Menengok kembali: C++ virtual function, static_cast, dan dynamic_cast.
edi ermawan, http://offground.wordpress.com
Alasan utama kenapa sebuah fungsi perlu dibuat virtual adalah agar fungsi tersebut bisa dideklarasikan ulang di kelas turunannya. Atau istilah lainnya adalah meng-override fungsi. Apa cuma itu tujuannya?. ada lagi, yaitu berhubungan dengan apa yang dinamakan dynamic binding di C++. Sebenarnya apakah virtual fungsi itu ?, something to eat?
Virtual fungsi :
- Anggota dari sebuah kelas
- Dideklarasikan dengan awalan virtual
- Virtual fungsi yang di-override dikelas turunannya, pada umumnya berbeda implementasinya dengan virtual fungsi di kelas induk.
- Fungsi bisa dibedakan type kelas mana yang memanggil.
Untuk mengerti lebih dari itu (khususnya point no.4 ), sepertinya sangat perlu untuk dicoba
. Misalnya terdapat sebuah kelas dasar bernama DrawableObject, yang merupakan kelas dasar untuk semua objek bangun 2D (persegi, lingkaran, persegi panjang, dll.).
Kelas Induk
class DrawableObject
{
public:
DrawableObject();
~DrawableObject();
virtual void Draw();
virtual void CountArea();
virtual void CountKeliling();
void SetName(char* str);
char* GetName();
private:
char* mName;
};
DrawableObject::DrawableObject()
{
//constructor
}
DrawableObject::~DrawableObject()
{
//destructor
}
void DrawableObject::Draw()
{
cout<<"Draw dari base kelas DrawableObject"<<endl;
}
void DrawableObject::CountArea()
{
cout<<"Count Area dari base kelas DrawableObject"<<endl;
}
void DrawableObject::CountKeliling()
{
cout<<"Count Keliling dari base kelas DrawableObject"<<endl;
}
void DrawableObject::SetName(char* str)
{
mName=str;
}
char* DrawableObject::GetName()
{
return mName;
}
Kelas DrawableObject memiliki 3 virtual fungsi yaitu Draw(), CountArea() dan CountKeliling(). Setelah membuat base kelas, coba kita buat kelas turunannya yaitu kelas persegi dan kelas Lingkaran, yang meng-override fungsi-fungsi virtual yang ada di base kelas.
Kelas persegi:
class Persegi:public DrawableObject
{
public:
Persegi(float& p,float& l);
~Persegi();
virtual void Draw();
virtual void CountArea();
virtual void CountKeliling();
private:
float mPanjang;
float mLebar;
};
Persegi::Persegi(float& p,float& l)
{
mPanjang=p;
mLebar=l;
}
Persegi::~Persegi()
{
//
}
void Persegi::Draw()
{
cout<<"Mengambar persegi"<<endl;
}
void Persegi::CountArea()
{
cout<<"Luas persegi: "<<mPanjang*mLebar<<endl;
}
void Persegi::CountKeliling()
{
cout<<"Keliling persegi: "<<2*(mPanjang+mLebar)<<endl;
}
Kelas Lingkaran:
class Lingkaran:public DrawableObject
{
public:
Lingkaran(float& r);
~Lingkaran();
virtual void Draw();
virtual void CountArea();
virtual void CountKeliling();
private:
float mJariJari;
};
Lingkaran::Lingkaran(float& r)
{
mJariJari=r;
}
Lingkaran::~Lingkaran()
{
cout<<"destructor lingkaran dieksekusi"<<endl;
}
void Lingkaran::Draw()
{
cout<<"Mengambar Lingkaran"<<endl;
}
void Lingkaran::CountArea()
{
cout<<"Luas Lingkaran: "<<(22/7)*(mJariJari)*(mJariJari)<<endl;
}
void Lingkaran::CountKeliling()
{
cout<<"Keliling Lingkaran: "<<4*(22/7)*(mJariJari)<<endl;
}
Ketiga fungsi virtual di kelas induk diimplementasikan secara berbeda di kelas turunannya. Apakah saat fungsi hasil override tadi jika dijalankan juga akan menjalankan baris kode di fungsi kelas induk?, jawabannya: tidak.
int main()
{
DrawableObject* persegi;
DrawableObject* lingkaran;
persegi=new Persegi(*(new float(2)),*(new float(3)));
lingkaran=new Lingkaran(*(new float(7)));
persegi->Draw();
persegi->CountArea();
lingkaran->Draw();
lingkaran->CountArea();
system("pause");
}
Hasil run:
Mengambar persegi
Luas persegi: 6
Mengambar Lingkaran
Luas Lingkaran: 147
Press any key to continue . . .
Dari hasil ini dapat disimpulkan kode fungsi virtual di kelas induk tidak dijalankan. Dengan virtual fungsi, saat run-time program bisa membedakan objek mana yang memanggil fungsi tersebut. Misal ada sebuah fungsi sebagai berikut:
void GambarSemuaObject(vector<DrawableObject*> vectObject)
{
DrawableObject* obj;
for(vector<DrawableObject*>::size_type i=0;i!=vectObject.size();i++){
obj=(DrawableObject*)(vectObject[i]); // casting ala C
obj->Draw(); //<- memanggil draw sesuai tipe dari obj, persegi atau lingkaran
obj->CountArea();
obj->CountKeliling();
cout<<endl;
}
}
Dipanggil di main:
int main()
{
vector<DrawableObject*> vect;
DrawableObject* persegi;
DrawableObject* lingkaran;
persegi=new Persegi(*(new float(2)),*(new float(3)));
lingkaran=new Lingkaran(*(new float(7)));
vect.push_back(persegi) ;
vect.push_back(lingkaran);
GambarSemuaObject(vect);//<--------pemanggilan
system("pause");
}
Hasil eksekusi :
Mengambar persegi
Luas persegi: 6
Keliling persegi: 10
Mengambar Lingkaran
Luas Lingkaran: 147
Keliling Lingkaran: 84
Press any key to continue . . .
Fungsi GambarSemuaObject memiliki argumen input vector yang menampung pointer ke DrawableObject. Secara ajaib (hmm..memang fiturnya C++ ya :p ), pemanggilan Draw(), CountArea(), dan CountKeliling() di fungsi GambarSemuaObject akan disesuaikan dengan tipe objeknya secara implisit. Apakah itu objek tipe persegi atau lingkaran. Di istilah C++, ini dinamakan Dynamic Binding. Hal ini tidak akan bisa terjadi jika fungsi tersebut tidak didefinisikan virtual.
Untuk mencobanya, saya hilangkan keyword “virtual” di fungsi Draw() pada kelas DrawableObject. Hasil eksekusi akan berbeda:
Draw dari base kelas DrawableObject
apa Draw dari base kelas DrawableObject
Luas persegi: 4
Keliling persegi: 8
Draw dari base kelas DrawableObject
Luas Lingkaran: 147
Keliling Lingkaran: 84
Press any key to continue . . .
Hasil diatas menunjukkan bahwa sebuah fungsi yang tidak didefinisikan virtual akan selalu dijalankan di kelas turunannnya karena saat kompilasi “tipe kelas” dari fungsi tersebut sudah ditentukan (Static Binding).
static_cast, dynamic_cast.
Di kode diatas kita meng-casting DrawableObject sebagai berikut:
obj=(DrawableObject*)(vectObject[i]); // casting ala C
yang sama hasilnya dengan kode berikut ini:
obj=static_cast<DrawableObject*>(vectObject[i]);
jika keyword static_cast diganti dengan dynamic_cast sebagai berikut:
obj=dynamic_cast<DrawableObject*>(vectObject[i]);
Hasil run program juga masih sama. Lalu sebenarnya apa perbedaan static_cast dan dynamic_cast ?. Untuk kode diatas memang akan menghasilkan output yang sama karena baik objek lingkaran dan persegi adalah turunan dari DrawableObject( upcasting : konversi dari type kelas turunan ke kelas induk).
(gambar kelas hirarki)
Keyword static_cast dan dynamic_cast akan tampak perbedaannya jika kita melakukan downcasting (konversi dari type data kelas induk ke kelas turunannnya). Misalnya tedapat objek:
DrawableObject* obj=new DrawableObject();
Obj, akan di downcasting ke tipe kelas persegi, kode:
Persegi* p=static_cast<Persegi*>(obj);
p->CountArea();
dengan
Persegi* p=dynamic_cast<Persegi*>(obj);
p->CountArea(); //p=null baris ini akan error
akan memberikan hasil yang berbeda. Pada kode pertama p akan menunjuk ke objek dengan tipe DrawableObject sedangkan pada kode kedua, p akan bernilai NULL pointer. Dari sini dapat disimpulkan bahwa dynamic_cast melakukan pengecekan saat mengkonversi sedangkan static_cast tidak. Dynamic_cast sangat berguna saat konversi yang memerlukan pengecekan saat run-time, namun banyak referensi yang setuju bahwa penggunaan dynamic_cast mempengaruhi performance program (program jadi lambat dibandingkan saat konversi menggunakan static_cast). Penggunaan dynamic_cast bisa dihindari, namun kayaknya berhubungan dengan desain program secara menyeluruh :p. Hmm..ini sepertinya tema yang advanced. Lupakan saja (atau lain kali saja), mengerti sejauh ini sudah cukup dulu.
05/15/10-07:44:34 PM-eof-

artikel yang bagus dan menambah wawasan tentang C++ yang advance….terima kasih
agus
February 3, 2012 at 4:10 am
thank U , Agus…
oth3rside
February 15, 2012 at 6:05 am