সি++ এ বহুরূপতা কি, কেন এবং একটি ছোট Introduction to Virtual Function
আজ কথা বলবো বহুরূপতা নিয়ে। বহুরূপতা কি?
কখনো চিন্তা করেছেন কি আমরা মানুষরা একটি বহুরূপি প্রাণী? হ্যাঁ। ধরেন দেখি একটি পুরুষের ক্ষেত্রে। একটি পুরুষ একই সাথে একজন ছেলে, একজন বাবা, একজন ভাই, একজন স্বামী এবং একজন কর্মচারী। দেখেন কতগুলো রূপ। সবগুলোর চরিত্র কিন্তু আবার একেক গুলো থেকে আলাদা। একেক চরিত্রে তাকে একেক রকমের ব্যবহার করতে হয়। ঠিক একইভাবে একটি নারীও এরকম বহুরূপি। কখনও সে একজন মা, একজন মেয়ে, একজন বোন, একজন স্ত্রী অথবা একজন কর্মচারী। এটাকে বলে বহুরূপতা। এই বহুরূপতা আবার অবজেক্ট অরিয়েন্টেড প্রোগ্রামিং এও আছে। তখন এই বহুরূপতাকে প্রোগ্রামিং এর ভাষায় বলে Polymorphism. Polymorphism শব্দটির অর্থ হচ্ছে একাধিক রূপ অথবা Form থাকা।
অবজেক্ট অরিয়েন্টেড প্রোগ্রামিং এ এই Polymorphism অনেক গুরুত্বপূর্ণ একটি ফিচার। তাই সি++ OOP তেও এই Polymorphism বিশেষ ভূমিকা রাখে। Polymorphism তখনই হয় যেখানে অনেক গুলো class এর মধ্যে একটি Hierarchy থাকে এবং তারা Inheritance এর মাধ্যমে একে অপরের সাথে সম্পর্কিত থাকে।
সি++ Polymorphism এর কাজ হচ্ছে, যখন আমরা একটি মেম্বার ফাংশন কে কল করবো তখন সেই ফাংশনটি যে অবজেক্টের ধরনের উপর নির্ভর করে execute হবে সেই অবজেক্টের ফাংশন কে কল অথবা Invoke করা।
সি++ Polymorphism কে দুই ভাগে ভাগ করেছে —
১. কম্পাইল টাইম পলিমরফিসম(Compile time Polymorphism) এবং
২. রানটাইম পলিমরফিসম (Run time Polymorphism)
১. কম্পাইল টাইম পলিমরফিসম(Compile time Polymorphism) —
এই ধরনের Polymorphism Achieve করার জন্য আমাদের ফাংশন অভারলোডিং অথবা অপারেটর অভারলোডিং ব্যবহার করতে হয়।
# ফাংশন অভারলোডিং দিয়ে কম্পাইল টাইম পলিমরফিসম —
ফাংশন অভারলোড করার কিছু শর্ত আছে। সেগুলো হচ্ছে —
- একাধিক ফাংশনের নামে এক হতে হবে,
- কিন্তু এদের Parameters একে অপরের থেকে ভিন্ন হতে হবে
- এদের Arguments Type ও ভিন্ন হতে হবে।
উপরের শর্ত গুলো Fulfill হলে তখন একটি ফাংশন অভারলোড হয়। ফাংশনকে তার Arguments এর সংখ্যায় পরিবর্তন অথবা ধরনের পরিবর্তন এনেও অভারলোড করা যায়।
চলুন এবার দেখি কীভাবে একটি অভারলোডেড ফাংশন পলিমরফিসমের অংশ হতে পারে।
#include <iostream>
using namespace std;
class Numbers {
public:
// function with int parameter
void num(int x){
cout << "Value of x is: "<< x << endl;
}
// same function with double parameter
void num(double x){
cout << "Value of x is: "<< x << endl;
}
// same function with 2 int parameter
void num(int x, int y){
cout << "Value of x and y is: "<< x << ", " << y << endl;
}
};
int main(){
Numbers value;
// Here the polymorphism will work
// Which function is called will depend on the parameters passed
// Now we will call the first function
value.num(1);
// Now the second function
value.num(2.202);
// Now the third one
value.num(3, 4);
return 0;
}
আউটপুট দেখাবে —
Value of x is: 1
Value of x is: 2.202
Value of x and y is: 3, 4
উপরের কোডে আমরা ভিন্ন ভিন্ন Parameters গ্রহণ করে অথচ একই ফাংশন এর তিন রকমের ভ্যালু রিসিভ করতে এবং সেগুলোকে আউটপুট হিসেবে দিতে দেখেছি। এটাই হচ্ছে ফাংশন অভারলোডিং দিয়ে কম্পাইল টাইম পলিমরফিসম।
#অপারেটর অভারলোডিং দিয়ে কম্পাইল টাইম পলিমরফিসম —
সি++ এ অপারেটর গুলোকেও অভারলোড করা যায়। যেমন আমরা যোগ (+) অপারেটর ব্যবহার করে আমরা দুইটা স্ট্রিং ক্লাশ কে যুক্ত করতে পারি। যোগ (+) চিহ্নের কাজ হচ্ছে দুই বা দুই এর অধিক বস্তু অথবা নাম্বারকে যুক্ত করা। তাই যখনই একটি (+) অপারেটর দুইটা ইন্টেজার ভ্যালুর মাঝখানে বসিয়ে দেয়া হয় তখন এই (+) অপারেটরের কারনে অই দুইটা ইন্টেজার ভ্যালু যুক্ত হয়ে যায়।
#include <iostream>
using namespace std;
class Complex{
private:
int real, imag; // imag for imagination
public:
Complex(int r = 0, int i = 0) {real = r; imag = i;}
// This is automatically called when '+' is used between two Complex Objects
Complex operator + (Complex const & obj) {
Complex res;
res.real = real + obj.real;
res.imag = imag + obj.imag;
return res;
}
void print() {
cout << real << " + i " << imag << endl;
}
};
int main() {
Complex n1(1, 2), n2(4, 5);
Complex n3 = n1 + n2;
n3.print();
}
আউটপুট দেখাবে —
5 + i 7
উপরের কোডে (+) অপারেটর কে আমরা অভারলোড করেছি। এই (+) অপারেটরটির আসল কাজ হচ্ছে দুইটা নাম্বার (ইন্টেজার অথবা ফ্লোটিং পয়েন্ট নাম্বার) যোগ করা, কিন্তু উপরের কোডে আমরা এই (+) অপারেটর অভারলোড করে দুইটি কমপ্লেক্স অথবা ইমাজিনারি নাম্বার যোগ করেছি। এতে একই অপারেটরের দুই রকমের ব্যবহার দেখা গেছে। এটাকে অপারেটর অভারলোডিং বলে এবং এটাই অপারেটর অভারলোডিং দিয়ে কম্পাইল টাইম পলিমরফিসম।
২. রানটাইম পলিমরফিসম (Run time Polymorphism) —
এই টাইপের পলিমরফিসম শুধু ফাংশন অভাররাইডিং (Overriding)করে Achieve করা যায়। যখন একটি Derived অথবা Sub Class তার Base Class এর যেকোনো একটি মেম্বার ফাংশনকে সংজ্ঞায়িত করে তখন সেই Base Function টি override হয় এবং একে ফাংশন অভাররাইডিং বলে।
#include <bits/stdc++.h>
using namespace std;
// Base Class
class Parent {
public:
void print(){
cout << "Parent Print Function" << endl;
}
};
// Derived Class
class Child : public Parent {
public:
// We're defining the same member function already exists in Parent
void print(){
cout << "Child Print Function" << endl;
}
};
// main function
int main() {
// Object of Parent Class
Parent obj1;
// Object of Child Class
Child obj2 = Child();
// obj1 will now call the print() function from Parent Class
obj1.print();
// obj2 now will override the print() function in Parent and call the print() function from Child
obj2.print();
return 0;
}
আউটপুট হবে —
Parent Print Function
Child Print Function
ভার্চুয়াল ফাংশন (Virtual Function) —
Run time Polymorphism এর আরেকটি গুরুত্বপূর্ণ অংশ হচ্ছে ভার্চুয়াল ফাংশন। ভার্চুয়াল ফাংশন হচ্ছে একটি মেম্বার ফাংশন যেটাকে Base Class এর মধ্যে ডিক্ল্যায়ার করা হয় এবং Derived Class এ যেয়ে সেটা অভাররাইড হয়ে যায়। যখন কোনো Derived Class অবজেক্ট কে পয়েন্টার অথবা রেফারেন্স ব্যবহার করে Base Class এর সাথে রেফার করা হয়, তখন সেই অবজেক্টের জন্য Virtual Function কে কল করা যায় এবং সেটা সেই অবজেক্টকে Derived Class এর Function এর জন্য Execute করে দেয়।
ফাংশন কল করার জন্য রেফারেন্স অথবা পয়েন্টার এর টাইপ যেরকমই হোক না কেন, Virtual Function সবসময় এটা খেয়াল রাখে অবজেক্ট যেন সঠিক ফাংশনকে কল করছে। Virtual Function এ ব্যবহার করার জন্য ফাংশন গুলোকে Base Class এ Virtual কী-ওয়ার্ড ব্যবহার করা হয়।
এই Virtual Function এর কিছু রুলস আছে। সেগুলো হচ্ছে —
- Virtual Function কে অবশ্যই Class এর Public Section এ Declare করতে হবে।
- Virtual Function কখনোই Static অথবা অন্য Class এর Friend Function হতে পারবে না।
- Run time Polymorphism ব্যবহার করার জন্য Virtual Function কে সবসময় Base Class থেকে রেফার করার জন্য Pointer অথবা Reference ব্যবহার করতে হবে।
- Virtual Function এর প্রোটোটাইপ Base এবং Derived Class দুটোতেই এক হতে হবে।
- Virtual Function কে সমসময় Base Class এ ডিক্ল্যায়ার করতে হবে। Derived Class এ অভাররাইড করা জরুরী না, সেক্ষেত্রে Base Class এর ফাংশন ব্যবহার হবে।
- একটি Class এ Virtual Destructor থাকতে পারবে কিন্তু Virtual Constructor থাকতে পারবে না।
চলুন এবার আরেকটি প্রোগ্রাম দেখি —
// Example program
#include <iostream>
using namespace std;
class Base {
public:
virtual void show() {
cout << "Base Class" << endl;
}
};
class Derived : public Base {
public:
void show() {
cout << "Derived Class" << endl;
}
};
int main(void) {
Base *bp = new Derived;
bp -> show(); // Run-Time Polymorphism
return 0;
}
আউটপুট হবে —
Derived Class
উপরের প্রোগ্রামটি Virtual Function ব্যবহার করে Run time Polymorphism এর একটি উদাহরণ। এই প্রোগ্রামের আসল জিনিষটা হচ্ছে Derived Class এ show() Function কে Base class থেকে pointer ব্যবহার করে কল করা হয়েছে। Virtual Function গুলোকে Pointed অথবা referred object type এর উপর ডিপেন্ড করে কল করা হয়।
Virtual Function কেন ব্যবহার করবো?
Virtual Function আমাদেরকে Base class pointer গুলোর একটি লিস্ট তৈরি করতে এলাউ করে এবং Derived Class অবজেক্ট কোন টাইপের তা জানা ছাড়াই Derived Class এর ম্যাথোডকে কল করতে পারে।
মনে করেন, একটি কোম্পানির কর্মচারী ম্যানেজমেন্টের জন্য একটি ম্যানেজমেন্ট সফটওয়্যার। সেই সফটওয়্যার এর একটি Employee নামক Base class আছে, যেখানে Base class এর ভিতর raiseSalary(), transfer(), promote() ইত্যাদি এরকম কিছু ফাংশন আছে। এখন কোম্পানিতে বিভিন্ন লেভেলের কর্মচারী থাকে, কেও ম্যানেজার, কেও ইঞ্জিনিয়ার। তো তাদের প্রত্যেকেরই Base class এর Virtual Function গুলোর ভিন্ন রকম নিজের মতোন করে implementation থাকতে পারে। কিন্তু আমরা আমাদের সফটওয়্যারে, শুধু একটি কর্মচারীদের লিস্ট পাস করবো এবং Virtual Function ব্যবহার করে যখন যেটা দরকার পড়বে সেই ফাংশনকে কল করবো, এক্ষেত্রে Virtual Function এর জানতে হবে না কোন কর্মচারী কোন ধরনের কাজ করছে। যেমন আমরা raiseSalary() ফাংশন ব্যবহার করে খুব সহজেই সবার বেতন বাড়িয়ে দিতে পারছি, এক্ষেত্রে আমাদের জানতে হচ্ছে না কর্মচারীটি কি ধরনের কাজ করে।
class Employee {
public:
virtual void raiseSalary() {
// some codes goes here
}
virtual void promote() {
// some codes goes here
}
};
class Manager : public Employee {
virtual void raiseSalary() {
// Manage's specific raise salary law, salary increment may
// differ from other workers
}
virtual void promote() {
// Manager's specific promote
}
};
// ম্যানেজারের মতোণ এরকম অনেক কর্মচারী থাকতে পারে
// যাদের সেলারী বাড়ানো জন্য আলাদা সিম্পল রুলস থাকতে পারে
// এখানে emp[] হচ্ছে পয়েন্টার দের একটি অ্যারে।
void globalRaiseSalary(Employee *emp[], int n) {
for (int i = 0; i < n; i++)
emp[i] -> raiseSalary(); // Polymorphic Call: Calls
// raiseSalary()
// According to the actual
// object, not according to the
// type of pointer
}
globalRaiseSalary() এর মতোন এরকম অনেক অপারেশনস থাকতে পারে যেটা অবজেক্ট টাইপ জানা ছাড়াই শুধু মাত্র কর্মচারী লিস্ট থেকেই সব কাজ করে ফেলতে পারবে।
অবজেক্ট অরিয়েন্টেড প্রোগ্রামিং নিয়ে আমার আগের লিখা গুলো পড়তে পারবেন আমার ব্যক্তিগত ব্লগ এবং মিডিয়াম থেকে —
অবজেক্ট অরিয়েন্টেড সি++ এবং Access Modifiers
Public vs Private vs Protectedmedium.com
অবজেক্ট অরিয়েন্টেড সি++ এবং ইনহেরিটেন্স (Inheritance)
বাবার সম্পত্তি সন্তাররাই পাবে, এটাই সত্য। এটাকে বলে উত্তরাধিকার সূত্রে পাওয়া। ইংরেজীতে Inheritance (ইনহেরিটেন্স)। ঠিক…bit.ly
আজকে এই পর্যন্তই। ধন্যবাদ সাথে থাকার জন্য।
#হ্যাপি_প্রোগ্রামিং
আমার ব্যাক্তিগত ব্লগ —
বাংলা ভার্শন — https://with.dibakar.me/
ইংলিশ ভার্শন — https://with.dibakar.me/en/
আমাকে পাবেন —
ফেসবুকে — https://www.facebook.com/dipu.dibakar
মিডিয়ামে — https://medium.com/@iamdibakardipu
টুইটারে — https://twitter.com/iamdibakardipu
ইনস্টাগ্রামে — https://www.instagram.com/dibakardipu/
গিটহাবে — https://github.com/dibakarsutradhar
লিঙ্কড ইনে — https://linkedin.com/in/dibakardipu/