অপারেটর ওভারলোড (Operator Overload)

Jan 31, 2014

C++ এর যত অপারেটর আছে, +, -, /, *, =, <, >, <=, >=, «, » ইত্যাদি সবগুলোই একেকটা ফাংশন। হ্যাঁ, C++ এ অপারেটরগুলো একেকটা ফাংশনের মত কাজ করে। অর্থাৎ, + অপারেটরটা আসলে int add(int a, int b) { return a+b; } এরকম একটা ফাংশন। বাস্তবে এতোটা সরল না, আমি কেবল বোঝানোর জন্য এতো সরল করে বললাম। তবে এভাবে চিন্তা করতে পারলেই হবে যে প্রত্যেকটা অপারেটর আসলে একটা ফাংশনের মত করে কাজ করে।

C++ এ ফাংশন ওভারলোড করা যায়। আর যেহেতু অপারেটরগুলো একেকটা ফাংশনের মত কাজ করে, তাই এদেরকেও ওভারলোড করা যায়! প্রথমে দেখা যাক ফাংশন ওভারলোড কি।

C তে একই নামের একটা মাত্র ফাংশন থাকতে পারে। একই নামে একাধিক ফাংশন লেখা যায় না। C++ এইদিক থেকে ভিন্ন। এখানে একই নামের একাধিক ফাংশন থাকতে পারে। এর সুবিধাটা হল, আমরা যদি একটা ফাংশন লেখি add(int a, int b) যেটা দুইটা int যোগ করে int রিটার্ন করে, তাহলে C তে দুইটা double যোগ করে double রিটার্ন করে এমন একটা ফাংশন লিখতে হলে নতুন নামের ফাংশন লিখতে হয়। ফলে একই ধরনের কাজ করার জন্য একাধিক ফাংশন লিখতে হচ্ছে।

যদি এই কাজটা C++ এ করা হয়, তাহলে আমরা এই add নামেই অনেকগুলো ফাংশন লিখতে পারবো যতক্ষণ পর্যন্ত একটা থেকে আরেকটা ফাংশনের signature আলাদা হবে। কোন ফাংশনের signature হল ঐ ফাংশনের প্যারামিটারগুলো। দুটি ফাংশনের signature হুবহু এক না হলেই হল। অর্থাৎ, আমরা এরকম দুইটা ফাংশন লিখতে পারবো,

int add(int a, int b) { return a+b; }
double add(double a, double b) { return a+b; }

এখন প্রশ্ন হল, আমরা যদি add ফাংশন call করি তাহলে তাহলে কম্পিউটার বুঝবে কিভাবে কোনটার কাজ করতে হবে? উত্তর হল কম্পিউটার দেখবে যে আমরা কি আর্গুমেন্ট দিয়ে call করেছি। আমাদের আর্গুমেন্ট দুইটা যদি int টাইপের হয় তাহলে প্রথম ফাংশনটি কাজ করবে, আর যদি double হয় তাহলে দ্বিতীয়টি কাজ করবে। এভাবে কাজ করা কত সহজ হয়ে গেল! কোন টাইপের জন্য কোন ফাংশন সেটা মনে রাখার দরকার নাই! একই ধরনের কাজ করে এমন ফাংশনের একটা নাম হলেই হল, বাকি কাজ কম্পিউটার করে নিবে।

এখানে যেটা আসলে বোঝার বিষয় তা হল, আমরা একই নামের ফাংশনকে দিয়ে আমাদের ইচ্ছামত কাজ করাতে পারি। কখন কোন ফাংশন কাজ করবে সেটা নির্ভর করে কোন ফাংশনের প্যারামিটারের সাথে আমাদের দেওয়া আর্গুমেন্ট মিলছে তার উপর। অতএব একইভাবে, যেহেতু অপারেটরগুলো একেকটা ফাংশনের মত কাজ করে, তাই আমরা একই অপারেটরকে আমাদের ইচ্ছামত কাজে লাগাতে পারবো এবং কখন কিভাবে সেটা কাজ করবে তা নির্ভর করছে অপারেটরটিকে আমরা কিভাবে ব্যবহার করছি তার উপর।

C++ Standard Template Library (STL) এর সাথে নিজের বানানো কোন class ব্যবহার করতে গেলে প্রায়ই অপারেটর ওভারলোড করতে হয়। আর এজন্য যে অপারেটরটা ওভারলোড করতে হয় সেটি হল < (less than) অপারেটর। কেন সেটা করতে হয় দেখা যাক।

ধরা যাক আমরা নিয়ে কাজ করব। সেট কি সেটা বিস্তারিত জানার দরকার নেই। এটুকু জানলেই হবে যে সেট এমন একটা কন্টেইনার যেখানে ডাটা insert করলে সেগুলো নিজে থেকেই sorted হয়ে থাকে। এখন আমরা যদি primitive টাইপের (int, double, char, long long ইত্যাদি) ডাটার সেট ব্যবহার করি, তাহলে কম্পিউটার সেগুলোকে সহজেই তুলনা করে নিতে পারে যে কোনটা থেকে কোনটা বড় এবং ছোট এবং সেই অনুযায়ী ডাটাগুলোকে সর্ট করে ফেলে। কিন্তু আমরা যদি আমাদের নিজেদের লেখা কোন class এর সেট ব্যবহার করতে চাই যেমন set mySet; তাহলে কম্পিউটার কিভাবে বুঝবে যে কোনটা ছোট আর কোনটা বড়? কোনটাকে সে আগে রাখবে আর কোনটাকে সে পরে রাখবে? এইজন্য কম্পিউটার যেটা করে তা হল < অপারেটর দিয়ে দুইটা class এর মধ্যে তুলনা করে দেখে কোনটা ছোট আর কোনটা বড়।

এখন আমাদের class টি যদি এমন হয় যে সেটি 2D plane এর একটা পয়েন্টকে নির্দেশ করে, তাহলে কোনটা ছোট আর কোনটা বড় সেটা বুঝবে কিভাবে? সেই জন্য আমরা < অপারেটর ওভারলোড করব এবং কোনটাকে ছোট বলবে আর কোনটা বড় বলবে সেটা ঠিক করে দিব। তখন যদি < অপারেটরের দুই পাশে MyClass টাইপের দুইটা অবজেক্ট থাকে, তখন ফাংশনের ক্ষেত্রে যেরকম হত সেরকমভাবে আমাদের ওভারলোড করা অপারেটরটি দিয়ে তুলনা করবে। শুধুমাত্র যে সেটই অপারেটর ব্যবহার করবে তা না। priority_queue, map এরাও < অপারেটর ব্যবহার করে। যেখানেই একটা অবজেক্ট আরেকটা অবজেক্টের থেকে ছোট নাকি বড় তা বিবেচনা করতে হয় তখনই অপারেটর ওভারলোড করতে হয়। আর এই কাজটা আমাদের জন্য সহজ করার জন্য STL এ কেবল < অপারেটর দিয়েই সব করা হয়।

এখন দেখা যাক কিভাবে আমরা অপারেটর ওভারলোড করব। নিচের কোডটুকু দেখা যাক।

class Point{
public:
    int x, y;
    bool operator < (const Point& p) const
    {
        if(x != p.x) return x < p.x;
        else return y < p.y;
    }
}

এখানে আমরা Point নামে একটা class ডিফাইন করেছি যেটা 2D প্লেনের একটা পয়েন্টকে x এবং y কোঅর্ডিনেটের মাধ্যমে নির্দেশ করবে। এখানে x এবং y দুটি ভেরিয়েবল ডিক্লেয়ার এর পরের লাইন খেয়াল করলে দেখা যাবে সেখানে আমরা একটা ফাংশন লিখেছি। শুরুতেই বলেছিলাম, C++ এ অপারেটরগুলো একেকটা ফাংশনের মত কাজ করে। আমরা এখানে সেই ফাংশনটাই লিখবো যেন কম্পিউটার জানে যে আমরা কিভাবে আমাদের class এর দুটি অবজেক্টকে তুলনা করতে চাচ্ছি।

প্রথমেই রিটার্ন টাইপ লেখা হয়েছে। < অপারেটর দুটি অবজেক্টের মধ্যে তুলনা করে বলবে যে প্রথম অবজেক্টটি দ্বিতীয় অবজেক্টটি থেকে ছোট নাকি না। প্রথম অবজেক্টটা ছোট হলে true রিটার্ন করবে, আর না হলে false রিটার্ন করবে। অতএব রিটার্ন টাইপ হবে bool। এরপর আমাদের ফাংশনটার নাম লিখবো। আমাদের ফাংশনটা হল < অপারেটর। এটাকে লিখতে হয় operator < এভাবে। মাঝে space না দিলেও হবে। এরপর ফাংশনের প্যারামিটার লিখতে হবে। < অপারেটর একটা বাইনারি অপারেটর। অর্থাৎ এর দুটি অপারেন্ড থাকে যাদের মাঝে সে তুলনা করে। < এর বামে যেটি থাকবে, প্রথম অবজেক্ট, আর ডানে থাকবে , দ্বিতীয় অবজেক্ট। অতএব ফাংশনের দুটি প্যারামিটার থাকবে যেই দুটি হবে Point class এর অবজেক্ট। কিন্তু যেহেতু আমরা একটা অবজেক্ট থেকে ফাংশনটি call করব সেহেতু ঐ অবজেক্টটা আমাদের প্রথম প্যারামিটার হিসেবে থেকে যাচ্ছে।

এই ব্যাপারটি একটু চিন্তা করলেই বোঝা যাবে। ধরা যাক আমাদের Point class টির মধ্যে distance নামে একটা ফাংশন আছে যেটি দুটি পয়েন্টের মধ্যে দুরত্ব রিটার্ন করে। এখন যদি দুটি পয়েন্ট ডিক্লেয়ার করা হয়, Point A, B; এবং A থেকে B এর দুরত্ব নির্ণয় করতে হয়, তাহলে আমরা লিখবো, A.distance(B); তাহলে দুটি পয়েন্টের একটা A, আরেকটা B এবং এই স্টেটমেন্টটিতে A থেকে B এর দুরত্ব হিসাব করে রিটার্ন হবে। অর্থাৎ আমরা A থেকে distance ফাংশনটি call করছি এবং সেখানে B কে আর্গুমেন্ট হিসেবে পাঠাচ্ছি। তাহলে A তো আগে থেকেই আছে, সাথে B কে পাঠালে দুটি পয়েন্ট পেয়ে যাচ্ছি। এভাবেই < অপারেটরেও একটা অবজেক্ট তো আছেই, তার সাথে তুলনার জন্য আরেকটা অবজেক্ট পাঠাতে হবে।

এখন তাহলে কেবল দ্বিতীয় অবজেক্টটি দরকার। সেটিই লেখা হয়েছে const Point& p দিয়ে। অর্থাৎ Point class এর একটা অবজেক্টের রেফারেন্স এখানে আসবে যেটির নাম হল p। আর সেটির যেন কোনধরনের পরিবর্তন না হয় তাই তার আগে const লিখে দেওয়া হয়েছে। আর const টাইপের অবজেক্টের সাথে ব্যবহার করতে হলে ফাংশনের নামের শেষে const লিখে দিতে হয়। এগুলা মূলত সাবধানতা বজায় রাখার জন্য লেখা যেন কোনকিছু ভুল করে পরিবর্তন হয়ে না যায়। const এবং reference এ ক্লিক করলে আরো বিস্তারিত পাওয়া যাবে।

এরপর আসলো মূল কাজ, < অপারেটরটি কখন true রিটার্ন করবে আর কখন false রিটার্ন করবে সেটি বলে দিতে হবে। আগেই বলেছি, প্রথম অবজেক্টটি ছোট হলে true রিটার্ন করে আর না হলে false। তাহলে বিষয়টি যেখানে দাঁড়ালো তা হল, কখন আমরা প্রথম অবজেক্টকে ছোট বলব? ধরা যাক আমরা একটা পয়েন্টকে আরেকটা পয়েন্ট থেকে ছোট বলব যদি তার x coordinate ছোট হয়। আর যদি দুটির সমান হয় তাহলে যদি y ছোট হয় তাহলে আমরা পয়েন্টটিকে ছোট বলব। তাহলে সেই অনুযায়ী if দিয়ে লিখে দেওয়া যাক, if(x != p.x) অর্থাৎ আমরা যেই অবজেক্ট থেকে ফাংশন call করছি তার x যদি তার কাছে পাঠানো p এর x এর সমান না হয় তাহলে return x < p.x; অর্থাৎ x < p.x স্টেটমেন্টটি true হলে true, false হলে false রিটার্ন করবে। যদি x == p.x হয় তাহলে return y < p.y; এটা আশা করি আর বলতে হবে না! :)

ব্যস, হয়ে গেল আমাদের অপারেটর ওভারলোডিং! এভাবে করে আমরা অনেক প্রবলেম অনেক সহজে সলভ করে ফেলতে পারি। বিভিন্ন প্রবলেমে এরকম দেখা যায় যে একটা জিনিসের অনেকগুলো বৈশিষ্ট্য দেওয়া থাকবে। এরকম অনেকগুলা জিনিস থাকবে যাদেরকে সর্ট করতে হবে। তখন কোন বৈশিষ্ট্যকে কোনটার আগে প্রাধান্য দেওয়া হবে সেই অর্ডারটা বলে দেওয়া থাকবে। সেই অনুযায়ী class বানিয়ে অপারেটর ওভারলোড করে একটা vector এ রেখে sort ফাংশন call করলেই হয়ে গেল! Problem solved!

C++ এ যেহেতু অনেকগুলো অপারেটর আছে, এবং তাদের সবগুলোকেই ওভারলোড করা যায়, তাই অপারেটর ওভারলোডের আলোচনার ক্ষেত্রটা অনেক বড়। এই ব্যাপারে আরো বিস্তারিত জানা যাবে learncpp.com থেকে।

প্র্যাকটিস প্রবলেমঃ
১। Codeforces - F1 Champions