Lesson পড়তে ৯ মিনিট লাগবে

ক্লাস এবং অবজেক্ট (Classes & Objects)

যেকোনো ক্লাস (class) হলো মূলত একটি বাড়ির ব্লুপ্রিন্টের (blueprint) মতো — এটি আপনার ঘরের আকার (ডেটা বা data) এবং এর দরজাগুলো (মেথড বা methods) কেমন হবে তা নির্ধারণ করে দেয়, এবং তারপরই আপনি ওই নকশা অনুযায়ী আপনার ইচ্ছামতো বাড়ি বা অবজেক্ট (houses) তৈরি করতে পারেন

বিক্ষিপ্ত ডেটা (Scattered Data) থেকে সুসংগঠিত অবজেক্টে (Organized Objects) রূপান্তর

ধরুন, আপনি ব্যাংকিং সম্পর্কিত একটি অ্যাপ (banking app) তৈরি করছেন। এখন আপনি চাইলে এর প্রতিটি গ্রাহককে (customer) ট্র্যাক বা নজরে রাখার জন্য আলাদা আলাদা ভ্যারিয়েবল (separate variables) রাখতে পারেন — যেমন balance1, name1, balance2, name2 ইত্যাদি — কিন্তু এতে করে এটি খুব দ্রুতই একটি জগাখিচুড়ি বা মেসি (messy) অবস্থায় পরিণত হবে। তাই এর সমাধান হিসেবে আপনার মূলত এমন একটি BankAccount (ব্যাংক অ্যাকাউন্ট) ব্লুপ্রিন্টের (blueprint) প্রয়োজন পড়বে যা একই প্যাকেজের (clean package) মধ্যে এর সমস্ত ডেটা (data) (তার ব্যালেন্স বা balance, তার নাম বা name) এবং এর অপারেশনগুলোকে (operations) (ডিপোজিট বা deposit, উইথড্র বা withdraw) একসঙ্গে বান্ডেল (bundles) আকারে রাখতে সাহায্য করবে।

আর এই ব্লুপ্রিন্টটিকেই (blueprint) মূলত একটি ক্লাস বা class বলা হয়। ওই ক্লাসটি থেকে আপনি এরপর যে অ্যাকাউন্টটিই (account) তৈরি করবেন না কেন তাকে মূলত একটি অবজেক্ট (object) (বা ইনস্ট্যান্স বা instance) বলা হবে। এই ক্লাসটিই মূলত এর সমস্ত শর্ত বা নিয়মকানুনগুলোকে (rules) নির্ধারণ করে দেয়; আর এখানকার অবজেক্টগুলো (objects) মূলত একে ভিত্তি করেই বেঁচে থাকে বা কাজ করে।

class (ক্লাস) বনাম struct (স্ট্রাকচার)

সি++ (C++)-এ class এবং struct মূলত প্রায় একই (almost identical) জিনিস। এদের মধ্যে একমাত্র পার্থক্যটি (difference) হলো এদের ডিফল্ট অ্যাক্সেস লেভেলে (default access level):

  • struct — এখানকার মেম্বারগুলো (members) মূলত ডিফল্টভাবেই (by default) পাবলিক (public) হয়ে থাকে
  • class — এখানকার মেম্বারগুলো (members) মূলত ডিফল্টভাবেই (by default) প্রাইভেট (private) হয়ে থাকে

প্রথাগত বা সাধারণ নিয়ম (convention) অনুযায়ী, শুধু সাধারণ কোনো ডেটার বান্ডেলের (data bundles) ক্ষেত্রে মূলত (যেমন এক্স বা x ও ওয়াই বা y স্থানাঙ্কের কোনো পয়েন্ট বা Point) struct ব্যবহার করুন, আর যেকোনো ধরনের বিহেভিয়ার বা আচরণ (behavior) এবং ইনভ্যারিয়েন্টের (invariants) ক্ষেত্রে মূলত class-টিকে ব্যবহার করুন।

struct বনাম class — একমাত্র আসল পার্থক্যটি (The Only Real Difference)

#include <iostream>
using namespace std;
// struct: এর মেম্বারগুলো (members) ডিফল্টভাবেই (by default) পাবলিক (public) হয়ে থাকে
struct Point {
double x; // পাবলিক (public)
double y; // পাবলিক (public)
};
// class: এর মেম্বারগুলো (members) ডিফল্টভাবেই (by default) প্রাইভেট (private) হয়ে থাকে
class Secret {
int value; // প্রাইভেট বা private — এটিকে বাইরে থেকে কোনোভাবেই অ্যাক্সেস (access) করা যাবে না!
public:
void setValue(int v) { value = v; }
int getValue() const { return value; }
};
int main() {
Point p;
p.x = 3.0; // ঠিক আছে (Fine) — এটি ডিফল্টভাবেই (by default) পাবলিক (public)
p.y = 4.0;
cout << "Point: (" << p.x << ", " << p.y << ")" << endl;
Secret s;
// s.value = 42; // এরর বা ERROR: এখানকার value-টি মূলত প্রাইভেট বা private!
s.setValue(42);
cout << "Secret: " << s.getValue() << endl;
return 0;
}
Output
Point: (3, 4)
Secret: 42

কনস্ট্রাক্টরগুলো (Constructors) — অবজেক্টটিকে (Object) ঠিকভাবে তৈরি করা (Building the Object Right)

যেকোনো কনস্ট্রাক্টর (constructor) হলো মূলত একটি স্পেশাল ফাংশন (special function) যা একটি অবজেক্ট (object) তৈরি হওয়ার সময়ই রান (runs) করে। এর প্রধান কাজটিই হলো ওই অবজেক্টটিকে একেবারে শুরু (beginning) থেকেই একটি বৈধ বা ভ্যালিড অবস্থায় (valid state) নিয়ে আসা। যাতে করে অর্ধেকভাবে ইনিশিয়ালাইজ (half-initialized) করা কোনো অবজেক্ট (objects) বাইরে আনমনে ঘুরে বেড়াতে না পারে।

সি++ (C++) মূলত এক্ষেত্রে আপনাকে বেশ কয়েক ধরনের কনস্ট্রাক্টরের স্বাদ (flavors) নেওয়ার সুযোগ দেয়:

  • ডিফল্ট কনস্ট্রাক্টর (Default constructor) — এর কোনো প্যারামিটার (parameters) নেই, এটি স্বাভাবিকভাবেই একটি বুদ্ধিমান এবং ডিফল্ট (sensible default) কনস্ট্রাক্টর তৈরি করে
  • প্যারামিটারাইজড কনস্ট্রাক্টর (Parameterized constructor) — এটি নিজে থেকেই বিভিন্ন প্যারামিটারের মানগুলোকে (specific values) যুক্ত বা ইনিশিয়ালাইজ (initialize) করার জন্য আর্গুমেন্ট (arguments) নিয়ে থাকে
  • মেম্বার ইনিশিয়ালাইজার লিস্ট (Member initializer list) — এটি মূলত কনস্ট্রাক্টরের বডিটি (constructor body) রান (runs) করার আগেই এর মেম্বারগুলোকে (members) মান যুক্ত বা ইনিশিয়ালাইজ (initializes) করে দেয় (এটি অনেক ফাস্ট বা faster, এবং বেশিরভাগ const/reference মেম্বারগুলোর জন্য এটি দরকার বা required হয়)

BankAccount — একটি পূর্ণাঙ্গ বা কমপ্লিট ক্লাস (A Complete Class)

#include <iostream>
#include <string>
using namespace std;
class BankAccount {
private:
string owner;
double balance;
int accountId;
public:
// মেম্বার ইনিশিয়ালাইজার লিস্টের (member initializer list) সাথে কনস্ট্রাক্টর (Constructor)
BankAccount(const string& name, double initial, int id)
: owner(name), balance(initial), accountId(id) {
cout << "Account created for " << owner << endl;
}
// টাকাটি ডিপোজিট (Deposit) করা
void deposit(double amount) {
if (amount > 0) {
balance += amount;
cout << "Deposited $" << amount << endl;
}
}
// টাকাটি তুলে নেওয়া বা উইথড্র (Withdraw) করা
bool withdraw(double amount) {
if (amount > 0 && amount <= balance) {
balance -= amount;
cout << "Withdrew $" << amount << endl;
return true;
}
cout << "Insufficient funds!" << endl;
return false;
}
// const মেথড (method) — এটি ওই অবজেক্টটিকে (object) কোনো প্রকার মডিফাই (modify) বা পরিবর্তন না করার প্রতিশ্রুতি (promises) দেয়
double getBalance() const {
return balance;
}
// const মেথড (method) — রিড-ওনলি বা শুধু পাঠযোগ্য অ্যাক্সেস (read-only access)
void display() const {
cout << "[" << accountId << "] " << owner
<< ": $" << balance << endl;
}
};
int main() {
BankAccount alice("Alice", 1000.0, 1001);
alice.deposit(500.0);
alice.withdraw(200.0);
alice.withdraw(5000.0);
alice.display();
return 0;
}
Output
Account created for Alice
Deposited $500
Withdrewed $200
Insufficient funds!
[1001] Alice: $1300
Note: বডিতে (body) অ্যাসাইন (assignment) করার পরিবর্তে সবসময় কনস্ট্রাক্টরে (constructors) মেম্বার ইনিশিয়ালাইজার লিস্ট (member initializer lists) ব্যবহার করুন। সরাসরি : owner(name), balance(initial) লিখলে, এটি প্রত্যেকটি মেম্বারকে (member) সরাসরি ইনিশিয়ালাইজ (initializes) করে দেয়। কিন্তু এর বদলে বডিতে (body) অ্যাসাইন করলে (যেমন owner = name;) এটি প্রথমে মেম্বারটিকে (member) ডিফল্ট-কনস্ট্রাক্ট (default-constructs) করে এবং এরপর অ্যাসাইন (assigns) করে — ফলে এটি মূলত ওই কাজটিকে ডাবল বা দ্বিগুণ (double work) করে দেয়। তবে const এবং রেফারেন্স মেম্বারগুলোর (reference members) ক্ষেত্রে অবশ্যই ইনিশিয়ালাইজার লিস্ট (initializer lists) ব্যবহার করা বাধ্যতামূলক বা required

অ্যাক্সেস মডিফায়ার (Access Modifiers) — এখানকার দরজাগুলোকে নিয়ন্ত্রণ (Controlling the Doors) করা

যেকোনো ক্লাসের (class) প্রতিটি মেম্বারেরই (member) একটি নির্দিষ্ট অ্যাক্সেস লেভেল (access level) থাকে:

  • পাবলিক (public) — এটিকে মূলত যে কেউ বা যেখান থেকেই খুশি সেখান থেকেই অ্যাক্সেস (access) করতে পারবে। এটি মূলত এমন একটি ইন্টারফেস (interface) যাকে আপনি বাইরের জগতের (world) সামনে তুলে (expose) ধরছেন।
  • প্রাইভেট (private) — এটিকে শুধুমাত্র ক্লাসটি (class) নিজেই অ্যাক্সেস (access) করতে পারবে। এটি মূলত ইন্টারনাল ডেটা (internal data) এবং যেকোনো হেল্পার ফাংশনের (helper functions) জন্য ব্যবহার করা হয়।
  • প্রোটেক্টেড (protected) — এক্ষেত্রে এখানকার মূল ক্লাস (class) এবং এর চাইল্ড বা ডেরাইভড (derived) ক্লাসগুলো এটিকে অ্যাক্সেস (access) করতে পারবে। মূলত ইনহেরিটেন্সগুলোর (inheritance) ক্ষেত্রে আমরা এটি আরও ব্যাপকভাবে দেখতে পাব।

এখানকার সোনার নিয়মটি (golden rule) হলো: ডেটাগুলোকে (data) মূলত প্রাইভেট (private) করে রাখা এবং সেগুলোর সাথে কাজ করার জন্য কিছু পাবলিক মেথড (public methods) প্রোভাইড বা প্রদান করা। একে এনক্যাপসুলেশন (encapsulation) বলা হয় — যেখানে যেকোনো একটি ক্লিন বা পরিপাটি ইন্টারফেসের (clean interface) পেছনে সমস্ত অগোছালো ইন্টারনাল বা অভ্যন্তরীণ (messy internals) জিনিসগুলোকে লুকিয়ে রাখা যায়।

এই this পয়েন্টারটি (Pointer)

যেকোনো নন-স্ট্যাটিক মেথডের (non-static method) ভেতরে, this হলো মূলত বর্তমান অবজেক্টের (current object) দিকে নির্দেশ করা একটি পয়েন্টার (pointer)। আপনার হয়তো সরাসরিভাবে (explicitly) এর প্রয়োজন খুব একটা পড়বে না, কিন্তু যখন কোনো প্যারামিটারের নাম (parameter name) আপনার মেম্বারের নামটিকে (member name) শ্যাডো বা ঢেকে (shadows) দেয়, বা যখন আপনি সরাসরি ওই আসল অবজেক্টটিকেই (object) রিটার্ন (return) করতে চান (যেমন মেথড চেইনিং (method chaining)-এর ক্ষেত্রে), তখন এটি বেশ কার্যকর (useful) হতে পারে।

this পয়েন্টার (this Pointer) এবং মেথড চেইনিং (Method Chaining)

#include <iostream>
#include <string>
using namespace std;
class Builder {
private:
string result;
public:
Builder() : result("") {}
// চেইনিং (chaining) সক্ষম বা enable করার জন্য *this রিটার্ন (Returns) করে
Builder& add(const string& text) {
result += text;
return *this; // অবজেক্টটিকে (object) ফেরত দেয় বা রিটার্ন দেয়
}
Builder& addLine(const string& text) {
result += text + "\n";
return *this;
}
void print() const {
cout << result;
}
};
int main() {
Builder b;
b.addLine("Hello")
.addLine("World")
.add("Done!");
b.print();
return 0;
}
Output
Hello
World
Done!

const মেথডগুলো (const Methods) — রিড-ওনলি বা শুধুমাত্র পাঠযোগ্য থাকার প্রতিশ্রুতি (The Read-Only Promise)

কোনো একটি মেথডকে (method) const হিসেবে মার্ক বা চিহ্নিত (Marking) করে রাখার অর্থ হলো কম্পাইলারকে (compiler) এটি বলা যে: "এই মেথডটি এর পেছনের কোনো মেম্বার ভ্যারিয়েবলকেই (member variables) মডিফাই বা পরিবর্তন (modify) করবে না।" এটি একটি অত্যন্ত গুরুত্বপূর্ণ বা ক্রুশিয়াল (crucial) কাজ, কারণ এটি আপনাকে সরাসরি const অবজেক্টগুলোতে (objects) এবং const রেফারেন্সগুলোতে (references) এই মেথডটিকে (method) কল করার অনুমতি দেয়। যেমন আপনার কাছে যদি একটি const BankAccount& থাকে, তবে আপনি নিশ্চিন্তে এর getBalance() (const) মেথডটিকে কল করতে পারবেন, কিন্তু deposit() (non-const) মেথডটিকে কখনোই নয়।

ডেস্ট্রাক্টরগুলো (Destructors) — ক্লিন আপ বা পরিষ্কার (Cleaning Up) করা

যখন কোনো অবজেক্ট ডেস্ট্রয় বা ধ্বংস (destroyed) হয়ে যায় (যেমন স্কোপের বা scope-এর বাইরে চলে গেলে, ডিলিট বা deleted হয়ে গেলে ইত্যাদি), তখন একটি ডেস্ট্রাক্টর (destructor) স্বয়ংক্রিয়ভাবে (automatically) রান বা চলতে শুরু করে। এর নামটি হলো ~ClassName()। কোনো সাধারণ ক্লাসের (simple classes) ক্ষেত্রে, কম্পাইলারের (compiler-generated) সাহায্যে জেনারেট হওয়া ডেস্ট্রাক্টরটিই মূলত যথেষ্ঠ। শুধুমাত্র ওই ক্লাসগুলো (class) যদি বিভিন্ন হিপ মেমরি (heap memory), ফাইল হ্যান্ডেলগুলো (file handles), বা নেটওয়ার্ক কানেকশনের (network connections) মতো রিসোর্সগুলোকে (resources) স্বয়ংক্রিয়ভাবে ম্যানেজ (manages) করতে চায়, ঠিক তখনই আপনার নিজস্ব (own) ডেস্ট্রাক্টর লেখার প্রয়োজন পড়ে।

ডেস্ট্রাক্টর (Destructor) — স্বয়ংক্রিয় ক্লিনআপ বা Automatic Cleanup

#include <iostream>
using namespace std;
class Logger {
private:
string name;
public:
Logger(const string& n) : name(n) {
cout << "[" << name << "] Created" << endl;
}
~Logger() {
cout << "[" << name << "] Destroyed" << endl;
}
void log(const string& msg) const {
cout << "[" << name << "] " << msg << endl;
}
};
int main() {
Logger a("Main");
a.log("Starting...");
{
Logger b("Inner");
b.log("Doing work");
} // b মূলত এখানেই ডেসট্রয় বা destroyed হয়ে যায় — এরপর এটি এর স্কোপের বা scope-এর বাইরে চলে যায়
a.log("Done");
return 0;
} // এরপর a ঠিক এখানেই ডেসট্রয় বা destroyed হয়ে যায়
Output
[Main] Created
[Main] Starting...
[Inner] Created
[Inner] Doing work
[Inner] Destroyed
[Main] Done
[Main] Destroyed
চ্যালেঞ্জ

ছোট কুইজ

সি++ (C++)-এ struct এবং class-এর মধ্যে একমাত্র পার্থক্য (ONLY difference) ঠিক কোনটি?
FunctionsInheritance & Polymorphism