এই পর্বে যা শিখবেন
- নিজের namespace, nested namespace (
shop::billing), alias আরusing-এর দুই রূপের পরিধি - Compiler একটি নাম কোথায় কোথায় খোঁজে — ভেতর থেকে বাইরে, আর ADL কী
#includeআসলে কী করে, include guard আর#pragma onceকেন লাগে- কী header-এ যায়, কী
.cpp-তে; One Definition Rule,inline,externআর forward declaration - একটি ছোট দোকানের প্রজেক্ট
money,cartআরmain-এ ভাগ করা, আর AI-এর লেখা header যাচাই
১ · বড় প্রোগ্রামের দুই সমস্যা — নাম আর ফাইল
রাকিব একটি মুদি দোকানের বিলিং সফটওয়্যার বানাচ্ছেন। প্রথম সপ্তাহে সব কোড ছিল এক ফাইলে, ২০০ লাইন। তিন মাস পরে কোড ৮,০০০ লাইন, দলে আরও দুজন। তখন দুটি সমস্যা একসাথে দেখা দিল:
- নামের সংঘাত: রাকিব টাকার হিসাবে
total()লিখেছেন, সহকর্মী নাদিয়া ঝুড়ির হিসাবেও লিখেছেনtotal(). দুটো একসাথে জোড়া লাগাতেই error. - ফাইলের বিশৃঙ্খলা: ৮,০০০ লাইনের এক ফাইলে একটি অক্ষর বদলালেও পুরোটা নতুন করে compile হয়। দুজন একই ফাইলে কাজ করলে একে অন্যের পরিবর্তন মুছে যায়। কোন অংশ কার, বোঝাই যায় না।
দুটি সমস্যা আলাদা, তাই সমাধানও দুটি। প্রথমটির উত্তর namespace — নামকে পরিবারে ভাগ করা। দ্বিতীয়টির উত্তর header আর source file-এ ভাগ করা — কাজকে ফাইলে ভাগ করা। ভালো C++ প্রজেক্টে এই দুই ভাগ সাধারণত একে অপরের সাথে মেলে: money.h আর money.cpp-এর সব কিছু থাকে money namespace-এ।
একটি বড় শপিং মলের কথা ভাবুন — বসুন্ধরা সিটির মতো। প্রতিটি দোকানের একটি নাম আর একটি ঠিকানা থাকে: “লেভেল ৫, ব্লক সি, দোকান ১২”। দুই তলায় দুটি দোকানের নাম “ফ্যাশন হাউস” হলেও কেউ বিভ্রান্ত হয় না, কারণ ঠিকানা আলাদা — এটাই namespace. আবার প্রতিটি দোকানের সামনে একটি সাইনবোর্ড থাকে, কী বিক্রি হয় তা লেখা (header); ভেতরের গুদাম আর হিসাবের খাতা থাকে পেছনের ঘরে, ক্রেতা দেখে না (source file). ক্রেতার জন্য সাইনবোর্ডই যথেষ্ট।
🏷️ নাম সাজানো — namespace
প্রশ্নের উত্তর দেয়: “এই total কোনটা?” পুরো নাম shop::money::total আর shop::cart::total — দুটো আলাদা। এটি compiler-এর নাম খোঁজার নিয়ম, ফাইলের সাথে সরাসরি সম্পর্ক নেই।
🗂️ ফাইল সাজানো — header/source
প্রশ্নের উত্তর দেয়: “কে কী দেখে, কোনটা কখন compile হয়?” Header-এ প্রতিশ্রুতি (ঘোষণা), .cpp-তে কাজ (সংজ্ঞা)। এটি build-এর নিয়ম — পর্ব ০৩-এর compile আর link-এর ওপর দাঁড়িয়ে।
পর্ব ০৪-এ আপনি std:: আর দুটি ছোট namespace দেখেছেন, আর পর্ব ০৩-এ দেখেছেন দুই ফাইলের প্রজেক্ট কীভাবে আলাদা compile হয়ে link হয়। এই পর্বে সেই দুই ধারণাকে পেশাদার মাত্রায় নিয়ে যাব।
#include, include guard, extern, ফাইলের ভেতরে লুকাতে static, আর multiple definition linker error — সবই C-র মতো। নতুন হলো namespace: C-তে সব নাম এক বিশাল সাধারণ জায়গায় থাকে বলে library-গুলো sqlite3_open-এর মতো উপসর্গ লাগায়; C++-এ সেই কাজ করে namespace. আরেকটি সূক্ষ্ম পার্থক্য: C99-এর inline-এর নিয়ম C++-এর inline-এর মতো নয়, তাই C-র অভ্যাস হুবহু নকল করবেন না। C-র দিক থেকে গভীরে দেখতে পড়ুন মডুলার প্রোগ্রাম, প্রিপ্রসেসর ও ম্যাক্রো আর স্কোপ ও স্টোরেজ ক্লাস.
২ · নিজের namespace — nested, alias আর using-এর পরিধি
একটি namespace বানানো খুব সহজ: namespace shop { … }. ভেতরে যা ঘোষণা করবেন — ফাংশন, ভ্যারিয়েবল, struct, class — সবার পুরো নামের শুরুতে বসবে shop::. বাইরে থেকে ডাকতে scope resolution অপারেটর :: লাগে। কয়েকটি গুরুত্বপূর্ণ বৈশিষ্ট্য:
- Namespace খোলা থাকে। একই নামের namespace একাধিকবার, এমনকি আলাদা ফাইলে খোলা যায় — সবকিছু মিলে একটাই namespace হয়।
money.hআরmoney.cppদুটোতেইnamespace shop::money { … }লেখা এভাবেই সম্ভব। (class-এর ক্ষেত্রে এমন হয় না — class একবারেই সম্পূর্ণ লিখতে হয়।) - Namespace-এর ভেতরে namespace. একে বলে nested namespace. আগে লিখতে হতো
namespace shop { namespace billing { … } }; C++17 থেকে এক লাইনেই লেখা যায়namespace shop::billing { … }. - লম্বা নামের ডাকনাম। Namespace alias:
namespace fs = std::filesystem;— এরপরfs::pathলিখলেই চলে। নতুন কিছু তৈরি হয় না, শুধু একটি ছোট নাম। - Global namespace. কোনো namespace-এর বাইরে লেখা সব নাম থাকে নামহীন সবার-বাইরের ঘরে। সেখানকার নাম স্পষ্টভাবে বোঝাতে সামনে শুধু
::লিখুন, যেমন::limit.
Compiler নাম খোঁজে কীভাবে — ভেতর থেকে বাইরে
কোডে একটি নাম, ধরুন limit, দেখলে compiler-কে ঠিক করতে হয় সেটি কোন limit. এই কাজকে বলে name lookup. নিয়মটি সহজ: সবচেয়ে কাছের ঘর থেকে শুরু করে বাইরের দিকে যাও, আর প্রথম যেখানে নামটা পাবে, সেখানেই থামো. নিচের ডায়াগ্রামে compiler-এর সাথে খুঁজে দেখুন:
1int limit = 1000; // global2namespace shop {3 int limit = 500;4 namespace billing {5 int fee = 20;6 bool allowed(int amount) {7 return amount + fee <= limit;8 }9 }10}11// main-এ: shop::billing::allowed(480)
| খুঁজছি | |
| কোথায় পেলাম | |
| মান |
- প্রথম নাম
amount. সবচেয়ে ভেতরের ঘর হলো ফাংশনের নিজের block আর parameter.amountসেখানেই আছে, খোঁজা শেষ। fee— এক ধাপ বাইরে। ফাংশনের ভেতরেfeeনেই। Compiler এক ঘর বাইরে, অর্থাৎshop::billing-এ গেল — ৫ নং লাইনে পেয়ে গেল।limit— দুই ধাপ বাইরে, তারপর থামা। ফাংশনে নেই,billing-এ নেই। বাইরেshop-এ ৩ নং লাইনেlimit = 500পাওয়া গেল, তাই খোঁজা এখানেই থামল। Global-এরlimit = 1000“ঢাকা পড়ে” গেল — compiler তাকে দেখলই না। ফলেallowed(480)হলো480 + 20 <= 500, অর্থাৎtrue.- যদি ৩ নং লাইন না থাকত? তখন
shop-এও না পেয়ে compiler আরও বাইরে, global namespace-এ যেত, আর পেত1000. একই কোড, একটি লাইন সরালেই অন্য ভ্যারিয়েবল! এজন্যই বড় প্রজেক্টে একই নাম বিভিন্ন স্তরে রাখা বিপজ্জনক, আর নিশ্চিত হতে চাইলে::limitবাshop::limitলিখে দেওয়া হয়। - বাইরে থেকে ভেতরে খোঁজা হয় না।
mainglobal namespace-এ থাকে। সেখান থেকে শুধুallowed(480)লিখলে compiler ভেতরের namespace-গুলোতে ঢুকে খোঁজে না — “'allowed' was not declared in this scope”. পুরো নামshop::billing::allowed, alias, বাusingলাগবে। খোঁজার দিক সবসময় ভেতর থেকে বাইরে।
using-এর দুই রূপ — আর তাদের পরিধি
পর্ব ০৪-এ using-এর দুই রূপ দেখেছেন। এবার আসল প্রশ্ন — এদের প্রভাব কতদূর ছড়ায়। উত্তর: যে scope-এ লিখবেন, সেই scope শেষ হওয়া পর্যন্ত।
| রূপ | কী আনে | ফাংশনের ভেতরে লিখলে | header-এর global অংশে লিখলে |
|---|---|---|---|
using declarationusing shop::billing::with_vat; | শুধু একটি নাম | শুধু সেই ফাংশনে, নিরাপদ | যত ফাইল include করে, সবখানে সেই নামটি — যতটা সম্ভব এড়ান |
using directiveusing namespace shop::billing; | পুরো namespace-এর সব নাম, এখন ও ভবিষ্যতে | শুধু সেই ফাংশনে, মোটামুটি চলে | সব include-কারীর ঘাড়ে চাপে, ফেরানো যায় না — কখনো নয় |
namespace aliasnamespace bill = shop::billing; | কিছুই আনে না, শুধু ছোট নাম | সবচেয়ে পরিষ্কার পথ | header-এ সাবধানে, সাধারণত নিজের namespace-এর ভেতরে |
এবার উপরের ডায়াগ্রামের কোডটাই চালিয়ে দেখুন। সাথে C++17-এর এক-লাইনের nested namespace, একটি alias আর ফাংশনের ভেতরে একটি using declaration যোগ করা হয়েছে:
#include <iostream>
int limit = 1000; // global namespace
namespace shop {
int limit = 500;
namespace billing {
int fee = 20;
bool allowed(int amount) { return amount + fee <= limit; }
}
}
namespace shop::billing { // C++17: একই namespace-এ আরও যোগ
int with_vat(int taka) { return taka + taka * 15 / 100; }
}
namespace bill = shop::billing; // alias: শুধু ছোট নাম
int main() {
std::cout << std::boolalpha << bill::allowed(490) << ' ' << bill::allowed(480) << '\n';
using shop::billing::with_vat; // শুধু একটি নাম, শুধু main-এর ভেতরে
std::cout << with_vat(200) << ' ' << ::limit << '\n';
}আউটপুট দুই লাইন: false true আর 230 1000. allowed(490)-এ 490 + 20 = 510, যা shop::limit-এর ৫০০-এর বেশি, তাই false; global-এর ১০০০ এখানে কোনো ভূমিকা রাখেনি। দ্বিতীয় namespace shop::billing block নতুন namespace নয় — আগেরটার সাথেই জুড়ে গেছে, তাই bill:: দিয়ে দুটো ফাংশনই পাওয়া যায়। using shop::billing::with_vat;-এর প্রভাব main-এর }-এ শেষ। আর ::limit স্পষ্টভাবে global ভ্যারিয়েবলটি চায়। বাস্তবে global ভ্যারিয়েবল যত কম রাখা যায় তত ভালো — এখানে শুধু খোঁজার নিয়ম দেখাতে রাখা হয়েছে।
৩ · ফাইলের গোপন সহকারী — anonymous namespace, static আর ADL
বড় প্রজেক্টে প্রতিটি .cpp ফাইলে কিছু ছোট সহকারী ফাংশন থাকে, যা শুধু সেই ফাইলের কাজে লাগে — যেমন money.cpp-এ শতাংশ বের করার একটি percent_of. সমস্যা হলো, সাধারণভাবে লেখা ফাংশনের নাম পুরো প্রোগ্রামে দৃশ্যমান: cart.cpp-তেও কেউ যদি নিজের জন্য percent_of লেখেন (একই parameter-সহ), link-এর সময় দুটো ধাক্কা খাবে।
সমাধান হলো সহকারীকে নামহীন একটি namespace-এ রাখা — anonymous namespace:
// money.cpp
namespace { // নামহীন: শুধু এই ফাইলের জন্য
long percent_of(long value, int pct) { return value * pct / 100; }
}
// এই ফাইলের বাকি অংশ percent_of সরাসরি ডাকতে পারে;
// অন্য কোনো .cpp ফাইল এটাকে দেখবেই না।Compiler প্রতিটি ফাইলের anonymous namespace-কে একটি গোপন, অনন্য নাম দেয়, আর তার ভেতরের সব কিছু পায় internal linkage — অর্থাৎ নামটি linker-এর সাধারণ তালিকায় যায় না, শুধু ওই translation unit-এর ভেতরে থাকে। বিপরীতটি external linkage: সাধারণ ফাংশনের নাম, যা অন্য ফাইল থেকে ডাকা যায়।
C-র static দিয়েও তো হয়?
হয়। static long percent_of(…) লিখলেও ফাংশনটি internal linkage পায় — C-তে এটাই একমাত্র পথ। তাহলে পার্থক্য কোথায়?
| বিষয় | static (ফাইলের স্তরে) | anonymous namespace |
|---|---|---|
| ফাংশন ও ভ্যারিয়েবল লুকানো | পারে | পারে |
struct/class লুকানো | পারে না — static struct বলে কিছু নেই | পারে — ভেতরের টাইপও শুধু এই ফাইলের |
| একসাথে অনেক কিছু | প্রতিটির সামনে আলাদা করে লিখতে হয় | এক block-এ সব |
| পড়ার সময় বোঝা | ঘোষণার পাশেই দেখা যায় — সুবিধা | বড় block হলে ভুলে যাওয়া সহজ |
Standard দুটোকেই বৈধ রাখে। কোন পথ ভালো, তা নিয়ে বড় প্রজেক্টের নিয়মও আলাদা: Google-এর স্টাইল গাইড .cc ফাইলের ভেতরের জিনিসের জন্য anonymous namespace বা static দুটোকেই মানে, আর LLVM-এর নিয়ম বলে ফাংশনের জন্য static, আর anonymous namespace যতটা সম্ভব ছোট রেখে শুধু টাইপের জন্য। আপনার দলের নিয়ম মেনে চলুন, তবে একটি নিয়ম সবখানে এক: header-এ anonymous namespace রাখবেন না. Header যত .cpp-তে ঢোকে, প্রত্যেকে নিজের আলাদা গোপন কপি পায় — ভ্যারিয়েবল হলে প্রতিটি ফাইলের মান আলাদা, যা প্রায়ই আসল উদ্দেশ্যের উল্টো।
Anonymous namespace দেখে মনে হতে পারে “কিছুই তো লিখিনি, তাহলে লাভ কী?” লাভটা ধরা পড়ে link-এর সময়। সহকারী ফাংশন লুকাতে ভুলে গেলে আজ হয়তো কিছু হয় না, কিন্তু ছয় মাস পরে অন্য কেউ একই নামের ফাংশন যোগ করলে হয় linker error, অথবা আরও খারাপ — অংশ ৬-এ দেখবেন নীরব ভুল আচরণ।
ADL — নামটি নিজের পরিবারকেও সাথে আনে
নাম খোঁজার আরেকটি নিয়ম আছে, যা প্রথমে জাদুর মতো লাগে। std::cout << "hi" লেখার সময় আপনি কখনো std::operator<< লেখেন না, তবু compiler ঠিক ফাংশনটি খুঁজে পায়। কারণ argument-dependent lookup (ADL): কোনো ফাংশন পদবি ছাড়া ডাকলে compiler সাধারণ খোঁজার পাশাপাশি argument-গুলোর টাইপ যে namespace-এ আছে, সেখানেও খোঁজে। cout-এর টাইপ std-এর, তাই std-এর ভেতরের operator<<-ও প্রার্থী হয়। একইভাবে দোকানের প্রজেক্টে to_text(price) লিখলে, price-এর টাইপ shop::money::Taka বলে compiler shop::money::to_text পেয়ে যায়। Andrew Koenig-এর নামে একে “Koenig lookup”-ও বলা হয়। সুবিধা হলো operator আর টাইপের সাথে জড়িত ফাংশন স্বাভাবিকভাবে লেখা যায়; ঝুঁকি হলো কখনো এমন ফাংশন বেছে নেওয়া হয় যার কথা আপনি ভাবেননি। তাই নিজের টাইপের সহকারী ফাংশন সবসময় সেই টাইপের namespace-এই রাখুন। Operator overloading পর্ব ২৩-এ ADL আবার কাজে লাগবে।
৪ · #include আসলে কী করে — কপি-পেস্ট আর include guard
এবার ফাইলের দিক। পর্ব ০৩-এ দেখেছেন preprocessor #include-এর জায়গায় header-এর লেখা বসিয়ে দেয়। কথাটা আক্ষরিক — এটি একটি বোকা কিন্তু বিশ্বস্ত কপি-পেস্ট মেশিন। সে C++ বোঝে না, namespace বোঝে না; শুধু ফাইল খুলে লেখা তুলে আনে। এই সরলতা থেকেই একটি বিখ্যাত সমস্যা জন্মায় — diamond include. ধাপে ধাপে দেখুন:
- শুরু
main.cppদুটি #include - প্রথমবার
money.hTaka বসল - তারপর
cart.hসে-ও money.h চায় - দ্বিতীয়বার
money.hTaka আবার! - সমাধান
guardদ্বিতীয়বার বাদ
// money.h namespace shop::money { struct Taka { long paisa = 0; }; } // cart.h #include "money.h" namespace shop::cart { money::Taka total(); } // main.cpp #include "money.h" #include "cart.h" int main() { /* ... */ }
namespace shop::money { struct Taka { long paisa = 0; }; } #include "cart.h" // এখনো বাকি int main() { /* ... */ }
namespace shop::money {
struct Taka { long paisa = 0; };
}
#include "money.h" // cart.h-এর প্রথম লাইন
namespace shop::cart { money::Taka total(); }
int main() { /* ... */ }
namespace shop::money { struct Taka { long paisa = 0; }; }
namespace shop::money { struct Taka { long paisa = 0; }; }
namespace shop::cart { money::Taka total(); }
$ g++ -std=c++23 -c main.cpp
money.h:2:12: error: redefinition of 'struct shop::money::Taka'
money.h:2:12: note: previous definition of 'struct shop::money::Taka'
#ifndef SHOP_MONEY_H #define SHOP_MONEY_H namespace shop::money { struct Taka { long paisa = 0; }; } #endif // প্রথমবার: SHOP_MONEY_H অজানা → ভেতরে ঢোকো, নামটা define করো // দ্বিতীয়বার: SHOP_MONEY_H জানা → #endif পর্যন্ত সব বাদ
#pragma once namespace shop::money { struct Taka { long paisa = 0; }; } // "এই ফাইলটি একটি translation unit-এ একবারই ঢুকবে" // GCC, Clang, MSVC — সবাই মানে, তবে standard-এর অংশ নয়
- তিনটি ফাইল, একটি নির্ভরতার ত্রিভুজ।
cart.h-এরTakaদরকার, তাই সে নিজেইmoney.hinclude করে — এটা ভালো অভ্যাস, প্রতিটি header নিজের প্রয়োজন নিজে মেটাবে।main.cppদুটোই চায়। গ্রাফ আঁকলে হীরার (diamond) মতো দেখায়: main থেকে money-তে দুই পথ। - প্রথম
#include. Preprocessormoney.hখুলে তার পুরো লেখা ঠিক ওই লাইনের জায়গায় বসাল। এখন translation unit-এstruct Taka-র একটি সংজ্ঞা। - দ্বিতীয়
#include—cart.h. তার লেখা বসল, আর তার প্রথম লাইনেই আরেকটি#include "money.h". Preprocessor মনে রাখে না যে এই ফাইল আগেই এসেছে — সে আবার কপি করবে। - Compile error. একই translation unit-এ
struct Taka-র দুটি সংজ্ঞা। Compiler বলছেredefinition. খেয়াল করুন, এটা compile error — একটি ফাইলের ভেতরের সমস্যা, linker পর্যন্ত যায়ইনি। অংশ ৬-এর linker error থেকে একে আলাদা করে চিনুন। - Include guard. Include guard হলো preprocessor-এর একটি ছোট শর্ত: “
SHOP_MONEY_Hনামটা যদি আগে define না হয়ে থাকে, তবেই ভেতরে ঢোকো, আর ঢুকেই নামটা define করো।” দ্বিতীয়বার শর্ত মিথ্যা, তাই পুরো লেখা বাদ। Macro-র নাম অনন্য রাখুন — প্রজেক্ট আর ফাইলের নাম মিলিয়ে। #pragma once. একই কাজ এক লাইনে, নাম বানানোর ঝামেলা নেই, নাম মিলে যাওয়ার ঝুঁকিও নেই।#pragma onceআনুষ্ঠানিক standard-এর অংশ না হলেও তিন বড় compiler-ই সমর্থন করে, আর বেশিরভাগ নতুন প্রজেক্ট এটিই ব্যবহার করে। দুর্বলতা একটাই: একই ফাইলের দুটি কপি বা symlink থাকলে compiler সেগুলোকে আলাদা ফাইল ভাবতে পারে। যেকোনো একটি পথ বেছে নিন — কিন্তু প্রতিটি header-এ একটি থাকতেই হবে.
ভিন্ন ভিন্ন
.cpp ফাইলে একই সংজ্ঞা থেকে বাঁচায় না — সেটা ODR-এর কাজ।AI আর পুরোনো কোডে প্রায়ই guard-এর নাম দেখবেন _MONEY_H_ বা __MONEY_H. C++-এ underscore দিয়ে শুরু হয়ে বড় হাতের অক্ষর, বা দুটি underscore থাকা নাম compiler আর standard library-র জন্য সংরক্ষিত। কাজ হয়তো চলবে, কিন্তু নিয়মভঙ্গ — SHOP_MONEY_H-এর মতো সাধারণ নাম লিখুন।
৫ · Header-এ কী, .cpp-তে কী — দোকানের প্রজেক্ট ভাগ করা
মূল নীতি একটি বাক্যে: header হলো প্রতিশ্রুতি, .cpp হলো সেই প্রতিশ্রুতি পূরণ। যে জিনিস অন্য ফাইলের জানা দরকার, তার ঘোষণা header-এ; আসল কাজ .cpp-তে। তবে কিছু জিনিস আছে যার পুরো দেহই header-এ রাখতে হয়, কারণ compiler-কে ব্যবহারের জায়গাতেই পুরোটা দেখতে হয়:
| জিনিস | কোথায় | কেন |
|---|---|---|
সাধারণ ফাংশনের ঘোষণা Taka with_vat(Taka); | header | ডাকার আগে compiler-কে টাইপ জানাতে |
| সাধারণ ফাংশনের দেহ | .cpp | পুরো প্রোগ্রামে একবারই থাকবে (ODR) |
struct/class-এর সংজ্ঞা | header | ব্যবহারকারীকে আকার আর সদস্য জানতে হয় |
inline ও constexpr ফাংশন, ছোট সহকারী | header | প্রতিটি ফাইলে দেহ লাগে; inline একাধিক কপি বৈধ করে |
| template-এর পুরো কোড | header | টাইপ জানার পরই কোড তৈরি হয় (পর্ব ২৮–২৯) |
ধ্রুবক inline constexpr int vat_percent = 15; | header | সব ফাইল একই মান দেখে |
| পরিবর্তনশীল global ভ্যারিয়েবলের সংজ্ঞা | একটি .cpp | header-এ শুধু extern ঘোষণা (অংশ ৭) |
| ফাইলের নিজস্ব সহকারী | .cpp, anonymous namespace-এ | বাইরে দেখানোর দরকার নেই |
using namespace …; | header-এ কখনো নয় | সব include-কারীতে ছড়ায় |
এবার রাকিবের দোকানের প্রজেক্টটি আসল রূপে দেখুন — পাঁচটি ফাইল। ট্যাব বদলে প্রতিটি ফাইল পড়ুন (এগুলো একসাথে এক প্রজেক্ট; পাতার রানার একবারে একটি ফাইল চালায়, তাই এখানে চালানোর বোতাম নেই):
#pragma once
#include <string>
namespace shop::money {
inline constexpr int vat_percent = 15; // সব ফাইলে একটাই মান
struct Taka { long paisa = 0; }; // টাইপের সংজ্ঞা: header-এ
inline Taka from_taka(long tk) { return {tk * 100}; } // ছোট, inline
Taka with_vat(Taka t); // শুধু ঘোষণা
std::string to_text(Taka t); // শুধু ঘোষণা
}#include "money.h" // নিজের header সবার আগে
#include <format>
namespace shop::money {
namespace { // শুধু এই ফাইলের সহকারী
long percent_of(long value, int pct) { return value * pct / 100; }
}
Taka with_vat(Taka t) { // প্রতিশ্রুতি পূরণ
return {t.paisa + percent_of(t.paisa, vat_percent)};
}
std::string to_text(Taka t) {
return std::format("{}.{:02} Tk", t.paisa / 100, t.paisa % 100);
}
}#pragma once
#include <string>
#include <vector>
#include "money.h" // Item-এর ভেতরে Taka আছে, তাই পুরো সংজ্ঞা লাগবে
namespace shop::cart {
struct Item { std::string name; money::Taka price; };
money::Taka total(const std::vector<Item>& items); // VAT-সহ মোট
}#include "cart.h"
namespace shop::cart {
money::Taka total(const std::vector<Item>& items) {
money::Taka sum;
for (const auto& it : items) sum.paisa += it.price.paisa;
return money::with_vat(sum);
}
}#include <print>
#include "cart.h" // cart.h নিজেই money.h আনে — guard থাকায় নিরাপদ
int main() {
namespace m = shop::money;
std::vector<shop::cart::Item> items{{"chal", m::from_taka(70)},
{"dal", {1550}}};
std::println("Total: {}", m::to_text(shop::cart::total(items)));
}# প্রতিটি .cpp আলাদা compile, শেষে link — header কমান্ডে নেই
g++ -std=c++23 -Wall -Wextra -c money.cpp
g++ -std=c++23 -Wall -Wextra -c cart.cpp
g++ -std=c++23 -Wall -Wextra -c main.cpp
g++ money.o cart.o main.o -o shop
./shop # Total: 98.32 Tkকয়েকটি অভ্যাস খেয়াল করুন। প্রতিটি .cpp নিজের header সবার আগে include করে — তাহলে header নিজে কোনো দরকারি #include ভুলে গেলে সঙ্গে সঙ্গে ধরা পড়ে। প্রতিটি header যা ব্যবহার করে তা নিজেই include করে (cart.h নিজে <vector> আনে, main.cpp-এর ওপর ভরসা করে না) — একে বলে “include what you use”. আর বড় সুবিধাটা হলো: শুধু money.cpp-এর ভেতরের হিসাব বদলালে শুধু সেটিই আবার compile হবে; money.h বদলালে তিনটিই — কারণ তিনটিই সেটা দেখে। Header যত স্থির, build তত দ্রুত।
পাতার রানার একবারে একটি ফাইলই চালাতে পারে। তাই নিচে একই প্রজেক্ট এক ফাইলে সাজানো হয়েছে — প্রতিটি namespace যেন একটি “ফাইল”, মন্তব্যে লেখা আছে আসলে কোনটা কোথায় থাকত:
#include <format>
#include <print>
#include <string>
#include <vector>
namespace shop::money { // money.h + money.cpp
inline constexpr int vat_percent = 15;
struct Taka { long paisa = 0; };
namespace { long percent_of(long v, int p) { return v * p / 100; } }
Taka with_vat(Taka t) { return {t.paisa + percent_of(t.paisa, vat_percent)}; }
std::string to_text(Taka t) { return std::format("{}.{:02} Tk", t.paisa / 100, t.paisa % 100); }
}
namespace shop::cart { // cart.h + cart.cpp
money::Taka total(const std::vector<money::Taka>& prices) {
money::Taka sum;
for (const auto& p : prices) sum.paisa += p.paisa;
return money::with_vat(sum);
}
}
int main() { // main.cpp
std::vector<shop::money::Taka> prices{{7000}, {1550}}; // chal, dal
std::println("Total: {}", to_text(shop::cart::total(prices))); // ADL
}আউটপুট: Total: 98.32 Tk. হিসাব: ৭০ টাকা + ১৫.৫০ টাকা = ৮,৫৫০ পয়সা; তার ১৫% পূর্ণসংখ্যায় ১,২৮২ পয়সা; মোট ৯,৮৩২ পয়সা। টাকা double-এ না রেখে পয়সায় long রাখা হয়েছে, যাতে দশমিকের গোলমাল না হয়। তিনটি জিনিস লক্ষ করুন: percent_of anonymous namespace-এ, তাই “money.cpp”-এর বাইরে লুকানো; cart namespace থেকে money::with_vat লিখতে shop:: লাগেনি, কারণ নাম খোঁজা ভেতর থেকে বাইরে গিয়ে shop-এর ভেতরে money পেয়েছে; আর শেষ লাইনে to_text পদবি ছাড়াই কাজ করেছে — ADL, কারণ argument-এর টাইপ shop::money-এর।
৬ · One Definition Rule — multiple definition আর inline
Include guard দেওয়ার পরও একটি error অনেককে অবাক করে। ধরুন রাকিব ভাবলেন, “with_vat তো ছোট ফাংশন, আলাদা .cpp-র কী দরকার?” — আর পুরো দেহটাই money.h-এ লিখে দিলেন, inline ছাড়া। Compile ঠিকঠাক, কিন্তু link-এ গিয়ে ধাক্কা। কেন, তা বুঝতে C++-এর একটি মৌলিক নিয়ম জানতে হবে — One Definition Rule (ODR):
- সাধারণ (non-inline) ফাংশন আর ভ্যারিয়েবল: পুরো প্রোগ্রামে ঠিক একটি সংজ্ঞা।
- Class, template,
inlineফাংশন ও ভ্যারিয়েবল: প্রতিটি translation unit-এ সর্বোচ্চ একটি, আর একাধিক ফাইলে থাকলে সবগুলো হুবহু এক হতে হবে।
- Header
money.hদেহসহ ফাংশন - ৩টি compile
*.oসবার ভেতরে একটি কপি - Link
ldmultiple definition - সমাধান ক
.cppদেহ সরানো - সমাধান খ
inlineবৈধ একাধিক কপি
#pragma once
namespace shop::money {
struct Taka { long paisa = 0; };
Taka with_vat(Taka t) { // দেহসহ, inline নয়!
return {t.paisa + t.paisa * 15 / 100};
}
}
$ g++ -std=c++23 -c money.cpp cart.cpp main.cpp ; তিনটিই সফল $ nm -C money.o cart.o main.o | grep with_vat T shop::money::with_vat(shop::money::Taka) ; money.o T shop::money::with_vat(shop::money::Taka) ; cart.o T shop::money::with_vat(shop::money::Taka) ; main.o
$ g++ money.o cart.o main.o -o shop
/usr/bin/ld: cart.o: in function `shop::money::with_vat(shop::money::Taka)':
cart.cpp:(.text+0x0): multiple definition of `shop::money::with_vat(shop::money::Taka)';
money.o:money.cpp:(.text+0x0): first defined here
collect2: error: ld returned 1 exit status
// money.h Taka with_vat(Taka t); // money.cpp Taka with_vat(Taka t) { return {t.paisa + t.paisa * 15 / 100}; } $ nm -C *.o | grep with_vat T shop::money::with_vat(shop::money::Taka) ; শুধু money.o U shop::money::with_vat(shop::money::Taka) ; cart.o চায়
// money.h inline Taka with_vat(Taka t) { return {t.paisa + t.paisa * 15 / 100}; } $ nm -C *.o | grep with_vat W shop::money::with_vat(shop::money::Taka) ; W = weak: একাধিক বৈধ, W shop::money::with_vat(shop::money::Taka) ; linker একটি রাখে $ g++ money.o cart.o main.o -o shop ; সফল
- দেখতে নিরীহ header.
#pragma onceআছে, তাই রাকিব নিশ্চিন্ত। কিন্তুwith_vat-এর পুরো দেহ header-এ, আর সামনেinlineনেই — অর্থাৎ এটি একটি সাধারণ ফাংশনের সংজ্ঞা. - তিনটি
.cpp, তিনটি সংজ্ঞা। Guard শুধু এক translation unit-এর ভেতরে দ্বিতীয়বার ঢোকা আটকায়।money.cpp,cart.cpp,main.cpp— প্রতিটি আলাদা translation unit, প্রতিটিতে header একবার করে ঢুকল, তাই প্রত্যেক object file-এwith_vat-এর একটি করে সংজ্ঞা (T). Compiler একবারে একটি ফাইলই দেখে, তাই কেউ আপত্তি করেনি। - Linker error. জোড়া লাগানোর সময় linker একই নামের তিনটি সংজ্ঞা পেল, আর জানে না কোনটা রাখবে:
multiple definition of …আরfirst defined here. এটি একটি linker error — ODR ভঙ্গের সবচেয়ে ভালো রূপ, কারণ অন্তত ধরা পড়েছে। - সমাধান ক — প্রচলিত ভাগ। Header-এ শুধু ঘোষণা, দেহ একটি
.cpp-তে। এখন সংজ্ঞা (T) একটাই, অন্যরা শুধু চায় (U). বড় ফাংশনের জন্য এটাই নিয়ম, কারণ দেহ বদলালে শুধু একটি ফাইল আবার compile হয়। - সমাধান খ —
inline. inline শব্দটির আধুনিক মানে “ফাংশনের কোড ডাকার জায়গায় বসাও” নয় (সে সিদ্ধান্ত optimizer নিজেই নেয়), বরং “এর একাধিক হুবহু এক সংজ্ঞা থাকা বৈধ, linker যেকোনো একটি রাখবে”. Linux-এnmতাইW(weak) দেখায়। ছোট ফাংশন আর header-only library-র জন্য এটাই পথ। Class-এর ভেতরে দেহসহ লেখা member function,constexprফাংশন আর template নিজে থেকেই এই সুবিধা পায়।
C++17-এর inline ভ্যারিয়েবল
একই সমস্যা ভ্যারিয়েবলেও। Header-এ int order_count = 0; লিখলে প্রতিটি .cpp-তে একটি সংজ্ঞা — আবার multiple definition. C++17-এর আগে একমাত্র পথ ছিল header-এ extern ঘোষণা আর একটি .cpp-তে সংজ্ঞা (অংশ ৭)। C++17 থেকে inline variable এসেছে: inline int order_count = 0; — header-এ থাকলেও পুরো প্রোগ্রামে একটাই ভ্যারিয়েবল। ধ্রুবকের জন্য inline constexpr int vat_percent = 15; লিখুন। (শুধু constexpr বা const লিখলেও namespace-এর স্তরে link error হয় না, কারণ সেগুলো নিজে থেকেই internal linkage পায় — তবে তখন প্রতিটি ফাইলে আলাদা কপি থাকে। inline constexpr দিলে সবাই একটাই বস্তু দেখে।)
দুটি আলাদা প্রতিরক্ষা, দুটি আলাদা সমস্যা: include guard → এক translation unit-এ দুবার ঢোকা (compile error redefinition); ODR + inline/.cpp-তে সরানো → একাধিক translation unit-এ একই সংজ্ঞা (linker error multiple definition). প্রায় সব header-এ দুটোরই দরকার।
সবচেয়ে বিপজ্জনক ODR ভঙ্গ কোনো error-ই দেয় না। দুটি .cpp-তে আলাদা আলাদা দেহসহ একই নামের inline ফাংশন, বা global namespace-এ একই নামের দুটি ভিন্ন struct Node — linker চুপচাপ একটি সংজ্ঞা রেখে দেয়, অন্য ফাইলের কোড ভুল সংজ্ঞায় চলে। Standard একে বলে “ill-formed, no diagnostic required” — অর্থাৎ ভুল, কিন্তু compiler-এর জানানোর দায় নেই। ফল undefined behavior. বাঁচার উপায়: সব কিছু namespace-এ, ফাইলের নিজস্ব টাইপ anonymous namespace-এ।
৭ · extern, forward declaration আর compile সময় বাঁচানো
extern — “ভ্যারিয়েবলটা আছে, অন্য কোথাও”
ফাংশনের ঘোষণা আর সংজ্ঞা আলাদা করা সহজ — দেহ থাকলে সংজ্ঞা, না থাকলে ঘোষণা। ভ্যারিয়েবলে int order_count; নিজেই একটি সংজ্ঞা। শুধু ঘোষণা করতে চাইলে লাগে extern:
// stats.h
namespace shop::stats { extern int order_count; } // ঘোষণা: জায়গা নেয় না
// stats.cpp
#include "stats.h"
namespace shop::stats { int order_count = 0; } // একমাত্র সংজ্ঞা
// C++17 থেকে বিকল্প: stats.h-এ inline int order_count = 0;নতুন কোডে inline ভ্যারিয়েবল সাধারণত সহজ। তবে মনে রাখুন — যেকোনো উপায়েই পরিবর্তনশীল global ভ্যারিয়েবল সব ফাইল থেকে বদলানো যায়, তাই ভুল খুঁজতে কঠিন। ভালো ডিজাইনে এমন জিনিস কোনো class-এর ভেতরে লুকানো থাকে (পর্ব ২০)।
Forward declaration — পুরো header না টেনে শুধু নামটা
অংশ ৫-এ দেখেছেন, header বদলালে যত ফাইল সেটা include করে, সবাই আবার compile হয়। বড় প্রজেক্টে একটি জনপ্রিয় header বদলানো মানে হাজার ফাইলের build. তাই অভিজ্ঞরা header-এ যতটা সম্ভব কম #include রাখেন। প্রধান হাতিয়ার forward declaration:
// receipt.h
#pragma once
namespace shop::cart { struct Item; } // forward declaration: "Item নামে একটি টাইপ আছে"
namespace shop::receipt {
void print_line(const cart::Item& item); // reference-এর জন্য পুরো সংজ্ঞা লাগে না
}
// receipt.cpp-তে #include "cart.h" — সেখানে সদস্য ব্যবহার করতে পুরো সংজ্ঞা লাগবেCompiler-এর কাছে শুধু নামটা জানা টাইপকে বলে incomplete type. এর reference বা pointer রাখা যায়, ফাংশনের ঘোষণায় parameter হিসেবে লেখা যায় — কিন্তু তার আকার জানা নেই, তাই সদস্য পড়া, object বানানো বা by-value সদস্য হিসেবে রাখা যায় না। ফলে receipt.h এখন cart.h-এর ওপর নির্ভর করে না: cart.h বদলালে receipt.h include করা ফাইলগুলো আর আবার compile হবে না। একটি সতর্কতা: std-এর নিজের টাইপ (যেমন std::string) নিজে forward declare করা নিষেধ — std namespace-এ কিছু যোগ করা undefined behavior. Stream-এর জন্য standard library নিজেই <iosfwd> দেয়।
- Include guard কী সমস্যা মেটায়? — একই header একটি translation unit-এ দুবার ঢুকে
redefinitionerror হওয়া আটকায়; ভিন্ন.cpp-র মধ্যে সংজ্ঞার সংঘাত আটকায় না। - One Definition Rule কী? — সাধারণ ফাংশন ও ভ্যারিয়েবলের পুরো প্রোগ্রামে একটি সংজ্ঞা; class, template আর inline জিনিসের প্রতিটি translation unit-এ একটি, আর সবগুলো হুবহু এক।
- Anonymous namespace আর
static-এর পার্থক্য? — দুটোই internal linkage দেয়; anonymous namespace টাইপও লুকাতে পারে, আর এক block-এ অনেক কিছু রাখা যায়। usingdeclaration আর directive-এর পার্থক্য? — declaration একটি নাম আনে, directive পুরো namespace খুলে দেয়; দুটোরই প্রভাব যে scope-এ লেখা সেখানে শেষ পর্যন্ত।- C++17-এর inline variable কেন এল? — header-এ ভ্যারিয়েবলের সংজ্ঞা রেখেও পুরো প্রোগ্রামে একটাই বস্তু পেতে,
extern+ আলাদা.cppছাড়াই।
৮ · সামনের পথ — C++20 modules আর import std;
এই পর্বের প্রায় সব সমস্যা — guard ভুলে যাওয়া, ODR, ধীর build, header-এর using ছড়িয়ে পড়া — একই উৎস থেকে আসে: #include হলো লেখা কপি করা। ১৯৭০-এর দশকের C থেকে আসা এই ব্যবস্থার পূর্ণ বিকল্প হিসেবে C++20-এ এসেছে modules. এক ঝলকে কেমন দেখায়:
// money.cppm — module interface (এক্সটেনশন compiler-ভেদে আলাদা: .cppm, .ixx …)
export module shop.money; // "এই ফাইল shop.money module"
export namespace shop::money { // শুধু export করা জিনিস বাইরে দেখা যায়
struct Taka { long paisa = 0; };
Taka with_vat(Taka t) { return {t.paisa + t.paisa * 15 / 100}; }
}
// main.cpp
import shop.money; // কপি নয়: আগে তৈরি interface পড়া
import std; // C++23: পুরো standard library এক লাইনে- একবার compile, বহুবার ব্যবহার: module একবার compile হয়ে একটি বাইনারি interface ফাইল বানায়;
importসেটাই পড়ে, লেখা আবার বিশ্লেষণ করে না। - Guard লাগে না: একই module দুবার import করলেও সমস্যা নেই।
- লুকানো থাকে লুকানো:
exportনা করা জিনিস আর module-এর ভেতরের macro বাইরে যায় না — header-এর মতো ছড়িয়ে পড়ে না। import std;C++23 standard-এ যোগ হয়েছে (C++20-এ ছিল না)। নতুন MSVC, Clang আর GCC-র সাম্প্রতিক version-এ চালানো যায়, তবে প্রায়ই বাড়তি flag বা build system-এর সাহায্য লাগে।
তাহলে এই পর্বের header কেন শিখলেন? কারণ পৃথিবীর বেশিরভাগ C++ কোড — এবং প্রায় সব library — এখনো header-ভিত্তিক, আর modules-এর জন্য compiler ও build system-এর সমর্থন ধীরে ধীরে পরিপক্ব হচ্ছে। এই কোর্সের কোড তাই header দিয়েই চলবে; modules-এর বিস্তারিত দেখবেন পর্ব ৪২-এ, আর CMake-এ বড় প্রজেক্ট সাজানো পর্ব ৪৭-এ।
৯ · AI-এর যুগে — AI-এর লেখা header যাচাই
“আমার প্রজেক্টটা কয়েকটা ফাইলে ভাগ করে দাও” — AI-কে এমন বললে সে দ্রুত সুন্দর দেখতে কয়েকটি ফাইল দেয়। কিন্তু AI ইন্টারনেটের অগণিত ছোট উদাহরণ থেকে শিখেছে, যেখানে সব কিছু এক ফাইলে, আর using namespace std; সবার ওপরে। সেই অভ্যাস header-এ এলেই সমস্যা। প্রোগ্রাম আজ হয়তো চলবে, কিন্তু দ্বিতীয় .cpp যোগ করার দিন বা কাউকে library হিসেবে দেওয়ার দিন ভাঙবে।
AI সাধারণত header-এ যেসব ভুল করে
| AI-এর ভুল | কখন ধরা পড়ে | ঠিক উপায় |
|---|---|---|
header-এ using namespace std; | অন্য কারও count, size, byte-এর সাথে ambiguous error, বা নীরবে ভিন্ন overload বাছাই | header-এ সবসময় std:: লিখুন |
| header-এ inline ছাড়া দেহসহ ফাংশন বা ভ্যারিয়েবল | দ্বিতীয় .cpp যোগ করলেই multiple definition | দেহ .cpp-তে, অথবা inline/inline constexpr |
guard বা #pragma once নেই | diamond include হলেই redefinition | প্রতিটি header-এর প্রথম লাইনে #pragma once |
_MONEY_H_-এর মতো সংরক্ষিত guard-নাম | সাধারণত কখনো না — কিন্তু নিয়মভঙ্গ | SHOP_MONEY_H |
header নিজের দরকারি #include ভুলে যায় | অন্য ক্রমে include করলে হঠাৎ 'string' in namespace 'std' does not name a type | যা ব্যবহার, তা নিজেই include; .cpp-তে নিজের header আগে |
| সব কিছু global namespace-এ, বা header-এ anonymous namespace | বড় হলে নামের সংঘাত; প্রতিটি ফাইলে আলাদা কপি | প্রজেক্টের নিজের namespace; anonymous শুধু .cpp-তে |
AI-কে ফাইল ভাগ করতে বলার সময় নিয়মগুলো আগেই বলে দিন, আর শেষে তাকে দিয়েই যাচাই করান। নিচের কাঠামোটি কপি করে নিজের প্রজেক্টে ব্যবহার করুন:
AI-এর ভাগ করা প্রজেক্ট হাতে পেলে — চেকলিস্ট
সেটা বোঝেন তিনি, যিনি জানেন
#include আসলে কপি-পেস্ট।ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন। এগুলো নিয়ম মুখস্থ থেকে বড় প্রজেক্টের নকশা বোঝার দিকে নিয়ে যাবে।
প্র ০১C preprocessor-এর #include মডেল কেন এত ধীর, আর C++20 modules সেটা মৌলিকভাবে কীভাবে বদলায়?
#include জন্মেছিল ১৯৭০-এর দশকে, C-র সাথে, যখন একটি প্রোগ্রাম মানে কয়েক হাজার লাইন আর header মানে কয়েকটি ঘোষণা। নকশাটি চমৎকার সরল: preprocessor ফাইল খুলে লেখা বসায়, compiler পরে পুরোটা পড়ে। C++ এই নকশা উত্তরাধিকারে পেয়েছে, কিন্তু C++-এর header আর হালকা নয় — template-এর কারণে standard library-র অনেক header-এ পুরো কোডই থাকে। ফলে একটি ছোট .cpp preprocess-এর পর হাজার হাজার লাইন হয়ে যায় (পর্ব ০৩-এ নিজে মেপেছেন)।
আসল সমস্যা গুণফলে। ধরুন প্রজেক্টে N-টি .cpp আর প্রতিটি গড়ে M লাইনের header টানে। মোট কাজ মোটামুটি N × M — একই <vector> হাজারবার পড়া হয়, বিশ্লেষণ হয়, template-এর একই কোড হাজারবার তৈরি হয়। আর compiler এই কাজ ভাগাভাগি করতে পারে না। কারণ macro: #include-এর আগে কেউ #define দিয়ে কিছু বদলে দিলে একই header-এর মানে বদলে যেতে পারে। তাই নিয়মমতো প্রতিটি translation unit-এ header নতুন করে পড়তেই হয়। Precompiled header আর ccache এই সমস্যার চারপাশে কাজ করে, সমাধান করে না।
Modules মূল ধারণাটাই বদলায়। একটি module নিজের interface একবার compile করে একটি বাইনারি রূপে (compiler-ভেদে নাম আলাদা — BMI, .pcm, .ifc) জমা রাখে। import করা ফাইল সেই তৈরি রূপটি পড়ে — লেখা কপি নয়, বিশ্লেষণ আবার নয়। Module-এর ভেতরের macro বাইরে যায় না, আর বাইরের macro module-এর মানে বদলাতে পারে না; তাই import-এর ক্রমও আর গুরুত্বপূর্ণ নয়। শুধু export করা নাম বাইরে দেখা যায়, ফলে “ভেতরের সহকারী” আপনাআপনি লুকানো থাকে। C++23-এর import std; পুরো standard library-কে এক module হিসেবে দেয়।
তাহলে সবাই কেন এখনই বদলায়নি? কারণ build-এর নিয়ম বদলে যায়। Header-এর যুগে প্রতিটি .cpp স্বাধীনভাবে, যেকোনো ক্রমে compile করা যেত। Module-এ আগে যে module import হবে তাকে আগে compile করতে হয় — build system-কে ফাইল পড়ে নির্ভরতার ক্রম বের করতে হয়। Compiler, CMake-এর মতো build system আর IDE-গুলোর এই সমর্থন তৈরি হতে C++20-এর পরেও কয়েক বছর লেগেছে, আর কোটি কোটি লাইনের পুরোনো কোড রাতারাতি বদলানো যায় না।
মূল উপলব্ধি: #include-এর ধীরগতি কোনো compiler-এর দুর্বলতা নয়, এটি “লেখা কপি করা” নকশার স্বাভাবিক ফল। Modules সেই নকশা বদলে “একবার বুঝে নাও, বারবার ব্যবহার করো”-তে নিয়ে যায় — কিন্তু ভাষার একটি ভিত্তি বদলাতে পুরো টুল-জগৎকে সাথে নিতে হয়।
প্র ০২একটি library-র header-এ using namespace std; থাকলে সেই library-র ব্যবহারকারীর জন্য ঠিক কী কী বিপদ হতে পারে?
নিজের .cpp-তে using namespace std; লেখা আপনার নিজের সিদ্ধান্ত — ঝুঁকিটাও আপনার। কিন্তু header-এ লিখলে সিদ্ধান্তটা আপনি অন্যের হয়ে নিচ্ছেন। #include কপি-পেস্ট বলে লাইনটি ব্যবহারকারীর প্রতিটি ফাইলে ঢুকে যায়, আর C++-এ using directive “বাতিল” করার কোনো উপায় নেই। ব্যবহারকারী জানেনই না তাঁর কোডে std-এর কয়েক হাজার নাম খুলে গেছে।
বিপদ তিন স্তরের। প্রথম, compile error: ব্যবহারকারীর নিজের count, distance, size বা data নামের কিছু থাকলে হঠাৎ ambiguous. একটি পরিচিত বাস্তব উদাহরণ C++17-এর std::byte: Windows-এর কিছু SDK header-এ global namespace-এ byte নামের একটি typedef আছে। using namespace std; থাকা প্রজেক্ট C++17-এ যেতেই অনেকে 'byte' is ambiguous error দেখেছিলেন — কোড না বদলেই, শুধু standard বদলে।
দ্বিতীয়, নীরব আচরণ বদল: এটি আরও খারাপ। ধরুন ব্যবহারকারী নিজের একটি max(long, long) লিখেছেন। std-এর template max খোলা থাকলে কোনো ডাকে compiler overload resolution-এ std-এরটাকে “ভালো মিল” মনে করে বেছে নিতে পারে — কোনো error নেই, শুধু ভিন্ন ফাংশন চলছে। তৃতীয়, ভবিষ্যতের ভাঙন: প্রতিটি নতুন standard std-এ নতুন নাম যোগ করে (std::size, std::ssize, std::print …)। আজ নিরাপদ কোড আগামী compiler-এ ভাঙতে পারে, আর ভাঙার কারণ খুঁজতে গেলে দোষী বের হয় একটি তৃতীয় পক্ষের header — যা ব্যবহারকারী বদলাতেও পারেন না।
এজন্য প্রায় সব বড় স্টাইল গাইড (C++ Core Guidelines-এর SF.7, Google, LLVM) header-এর global অংশে using namespace নিষেধ করে। নিয়মের পেছনের নীতি সাধারণ: header হলো একটি সর্বজনীন চুক্তি — সে শুধু নিজের প্রতিশ্রুতি দেবে, ব্যবহারকারীর পরিবেশ বদলাবে না। Header-এর ভেতরে নিজের ফাংশনের দেহে (যেমন inline ফাংশনের ভেতরে) using declaration রাখা তুলনামূলক নিরাপদ, কারণ তার প্রভাব সেই block-এ শেষ।
মূল উপলব্ধি: নিজের ঘরে জুতা খুলে রাখা আপনার ব্যাপার, কিন্তু অন্যের ঘরে ঢুকে তাঁর আসবাব সরানো নয়। Header-এর প্রতিটি লাইন ব্যবহারকারীর কোডের অংশ হয়ে যায় — তাই header লিখতে হয় অতিথির মতো ভদ্রভাবে।
প্র ০৩Chromium বা LLVM-এর মতো কোটি লাইনের C++ প্রজেক্ট namespace আর header কীভাবে সাজায়, আর সেখান থেকে ছোট দল কী শিখতে পারে?
বড় প্রজেক্টে namespace শুধু নামের সংঘাত ঠেকায় না — সেটি প্রজেক্টের মানচিত্রও। Chromium-এ মৌলিক সহকারী কোড থাকে base::-এ, নেটওয়ার্কের কোড net::-এ, ব্রাউজারের বিভিন্ন স্তর content::-এর মতো নামে, আর রেন্ডারিং ইঞ্জিন blink::-এ। LLVM-এ মূল লাইব্রেরি llvm::, আর C/C++ compiler অংশ clang::. সাধারণত namespace-এর নাম ফোল্ডারের নামের সাথে মেলে — net/ ফোল্ডারের ফাইল net namespace-এ। কোনো নাম দেখলেই বোঝা যায় কোডটা কোথায় থাকে, কে তার দায়িত্বে।
দ্বিতীয় স্তর হলো “ভেতরের” namespace. Library-গুলো প্রায়ই এমন সহকারী রাখে যা header-এ থাকতে বাধ্য (template-এর জন্য), কিন্তু ব্যবহারকারীর জন্য নয়। সেগুলো থাকে detail বা internal নামের namespace-এ — Boost-এ boost::detail, libstdc++-এ std::__detail. এটি আইনগত বাধা নয়, সামাজিক চুক্তি: “এটা ব্যবহার করবেন না, যেকোনো দিন বদলাতে পারে।” libstdc++ ABI version আলাদা রাখতে inline namespace-ও ব্যবহার করে — std::__cxx11, যা পর্ব ০৩-এর ABI আলোচনায় দেখেছেন।
তৃতীয় স্তর লিখিত নিয়ম। Google-এর C++ স্টাইল গাইড (Chromium মূলত এটিই মানে) using directive নিষেধ করে, header-এ namespace alias সীমিত রাখে, আর .cc ফাইলের নিজস্ব জিনিস anonymous namespace বা static-এ রাখতে বলে। LLVM-এর Coding Standards বলে “#include as little as possible”, আর anonymous namespace ছোট রাখতে — ফাংশনের জন্য static পছন্দ করে, কারণ বড় ফাইলে ঘোষণার পাশেই static দেখা যায়। দুই প্রজেক্টের নিয়ম আলাদা, কিন্তু দুটোই সচেতন সিদ্ধান্ত, আর দুটোই সব কোডে একইভাবে প্রয়োগ হয়।
এমন প্রজেক্টে header-এর নির্ভরতাও কঠোরভাবে নিয়ন্ত্রিত। “Include what you use” নীতি মানতে include-what-you-use নামের একটি আলাদা টুলও আছে, যা অপ্রয়োজনীয় বা বাদ পড়া #include খুঁজে দেয়। কারণ একটি জনপ্রিয় header-এ একটি অপ্রয়োজনীয় #include হাজার ফাইলের build-এ যোগ হয়।
ছোট দলের জন্য শিক্ষা: (১) প্রথম দিন থেকেই একটি শীর্ষ namespace রাখুন (প্রজেক্টের নাম), (২) ফোল্ডার আর namespace মেলান, (৩) ভেতরের জিনিস detail-এ, (৪) নিয়মগুলো এক পাতায় লিখে রাখুন, আর clang-format বা clang-tidy দিয়ে স্বয়ংক্রিয়ভাবে যাচাই করুন।
মূল উপলব্ধি: বড় প্রজেক্টে namespace আর header হলো প্রতিষ্ঠানের নকশা — কোন দল কোন অংশের মালিক, কে কার ওপর নির্ভর করতে পারে। নিয়মের চেয়েও গুরুত্বপূর্ণ হলো নিয়মটা সবখানে একইভাবে মানা।
প্র ০৪Header-only library-র ভেতরের কৌশল কী, আর কেন কিছু জনপ্রিয় library পরে header-only থেকে সরে এসেছে?
পর্ব ০৩-এ দেখেছেন header-only library মানে সব কোড header-এ, link করার কিছু নেই। এবার ভেতরের যন্ত্রটা দেখুন — এই পর্বের ODR জানার পরে সেটা পরিষ্কার। Header-এ রাখা প্রতিটি জিনিসকে ODR মানতে হয়, যেহেতু সেটা অনেক translation unit-এ ঢুকবে। তাই header-only library-তে সব non-template ফাংশন inline, সব ভ্যারিয়েবল inline বা constexpr, আর বাকিটা template বা class-এর ভেতরের member function — যা নিজে থেকেই inline. C++17-এর আগে inline variable ছিল না বলে লেখকদের static member-যুক্ত template-এর মতো কষ্টকর কৌশল লাগত; C++17 এই কাজ সহজ করেছে।
আরেকটি জনপ্রিয় নকশা হলো “single-file, implementation macro” — যেমন ছবি পড়ার stb library-গুলো। সব ফাইল header-এর মতো include করে ঘোষণা পায়; শুধু একটি .cpp-তে include-এর আগে একটি নির্দিষ্ট macro (যেমন STB_IMAGE_IMPLEMENTATION) define করলে সেখানে আসল দেহগুলো তৈরি হয়। অর্থাৎ ODR নিজেরাই হাতে মেনে চলা — একটি ফাইলেই সংজ্ঞা। fmt library আবার উল্টো পথ দেয়: সাধারণভাবে compile করা library, কিন্তু FMT_HEADER_ONLY define করলে header-only মোড।
সুবিধা: ব্যবহার সবচেয়ে সহজ — ফাইল কপি, include, শেষ; ABI মেলানোর ঝামেলা নেই, কারণ সবাই নিজের compiler আর flag দিয়ে compile করে; আর compiler সব কোড দেখতে পায় বলে inline আর optimization-এর সুযোগ বেশি। দাম: compile সময় — প্রতিটি translation unit পুরো library বিশ্লেষণ করে; library-র ভেতরের প্রতিটি সহকারী নাম ব্যবহারকারীর কাছে দৃশ্যমান; আর library-র ভেতরের একটি পরিবর্তনেও ব্যবহারকারীর পুরো প্রজেক্ট আবার compile হয়।
এই দামের কারণেই টেস্টিং library Catch2 তার version ৩-এ এসে single-header রূপ ছেড়ে সাধারণ compile করা library হয়েছে — বড় টেস্ট প্রজেক্টে প্রতিটি টেস্ট ফাইলে পুরো framework বিশ্লেষণ করা অসহনীয় ধীর হয়ে যাচ্ছিল। প্রবণতাটি তাই পরিষ্কার: যত বড় library আর যত বেশি ফাইলে ব্যবহার, header-only-র দাম তত বেশি। Modules এখানে মাঝামাঝি পথ দিতে পারে — বিতরণ সহজ, কিন্তু একবার compile.
মূল উপলব্ধি: header-only কোনো জাদু নয় — এটি ODR-কে inline দিয়ে সাবধানে মেনে চলার একটি কৌশল, যার দাম দিতে হয় compile সময়ে। কোন পথ নেবেন, তা নির্ভর করে library কত বড় আর কত জায়গায় ব্যবহার হবে তার ওপর।
প্র ০৫কোন ODR ভঙ্গ linker ধরতে পারে না, কেন সেগুলো “নীরব” undefined behavior, আর কীভাবে খুঁজে বের করা যায়?
Linker সহজে যে ভুল ধরে তা হলো দুটি সাধারণ (strong) সংজ্ঞা — multiple definition. কিন্তু ODR-এর দ্বিতীয় অংশ — “inline জিনিস আর class-এর সংজ্ঞা একাধিক ফাইলে থাকতে পারে, তবে সবগুলো হুবহু এক হতে হবে” — linker যাচাই করে না। Inline ফাংশনের সংজ্ঞা weak হিসেবে আসে, আর linker শুধু নাম মিলিয়ে যেকোনো একটি রেখে বাকিগুলো ফেলে দেয়। দেহ আলাদা কি না, সে দেখে না। Class-এর layout তো object file-এ নামই হয়ে থাকে না।
বাস্তব উদাহরণ ১: দুই ডেভেলপার দুটি .cpp-তে global namespace-এ নিজের নিজের struct Node লিখেছেন — একটিতে দুটি সদস্য, অন্যটিতে তিনটি, আর দুটোরই class-এর ভেতরে লেখা (তাই inline) member function আছে। Linker একটির member function রাখল; অন্য ফাইলের কোড ভুল আকারের object-এ সেটি চালাবে — মেমরি নষ্ট, ক্র্যাশ, অথবা শুধু ভুল ফল। উদাহরণ ২: একটি header-এর inline ফাংশন #ifdef NDEBUG দিয়ে আলাদা আচরণ করে, আর প্রজেক্টের কিছু ফাইল -DNDEBUG দিয়ে, কিছু ছাড়া compile হয়েছে। এক ফাইলের প্রত্যাশা অন্য ফাইলের সংজ্ঞায় চলে। একইভাবে debug আর release flag-এ compile করা library মেশালে standard library-র টাইপের layout পর্যন্ত আলাদা হতে পারে (যেমন libstdc++-এর debug mode).
Standard এসব ক্ষেত্রকে বলে “ill-formed, no diagnostic required”. কারণ বাস্তব: compiler একবারে একটি translation unit দেখে, তুলনা করার মতো অন্যটা তার সামনে নেই; আর ঐতিহ্যগত linker C++-এর টাইপ বোঝে না। তাই ভাষা দায়িত্বটা প্রোগ্রামারের ঘাড়ে দিয়েছে। ফল undefined behavior, যা অনেক সময় শুধু নির্দিষ্ট optimization level বা নির্দিষ্ট link ক্রমে দেখা দেয় — সবচেয়ে কঠিন বাগগুলোর একটি।
খোঁজার উপায় আছে, তবে কোনোটাই সম্পূর্ণ নয়। GCC-তে link-time optimization (-flto) চালু থাকলে -Wodr সতর্কতা কিছু ভিন্ন টাইপ-সংজ্ঞা ধরতে পারে, কারণ তখন সব translation unit-এর তথ্য একসাথে থাকে। AddressSanitizer global ভ্যারিয়েবলের কিছু ODR ভঙ্গ ধরতে পারে। কিন্তু প্রতিরোধই আসল ওষুধ: সব কোড প্রজেক্টের নিজের namespace-এ; ফাইলের নিজস্ব টাইপ ও ফাংশন anonymous namespace-এ; পুরো প্রজেক্টে একই compile flag আর macro; আর আলাদা flag-এ তৈরি library না মেশানো।
মূল উপলব্ধি: ODR-এর ভাঙন ধরা পড়লে সেটা সৌভাগ্য — multiple definition error আপনার বন্ধু। ভয় পাওয়ার মতো হলো সেই ভাঙন, যা কেউ ধরে না। Namespace আর anonymous namespace তাই শুধু সুন্দর সাজানো নয় — সেগুলো নিরাপত্তার বেড়া।
অনুশীলন
বেশিরভাগ কাজে কোড লিখতে হবে না — পড়া, অনুমান করা আর ভাবাই আসল কাজ।
-
কোন error, কোন ধাপে? প্রতিটি ক্ষেত্রে বলুন compile error, linker error, নাকি কোনো error নেই — (ক) guard ছাড়া
money.h-এstruct Taka, আরmain.cppএকে দুবার (সরাসরি ওcart.h-এর মাধ্যমে) পায়; (খ) guard-সহ header-এ inline ছাড়াint tax(int x) { return x / 10; }, দুটি.cppinclude করে; (গ) একই, কিন্তুinline int tax…; (ঘ) header-এconstexpr int vat = 15;, তিনটি.cppinclude করে।(ক) Compile error — একই translation unit-এ
redefinition of 'struct shop::money::Taka'. সমাধান guard বা#pragma once.(খ) Linker error — guard থাকলেও দুটি translation unit-এ দুটি সাধারণ সংজ্ঞা:
multiple definition of `tax(int)'.(গ) কোনো error নেই —
inlineএকাধিক হুবহু এক সংজ্ঞা বৈধ করে।(ঘ) কোনো error নেই — namespace-এর স্তরে
constexpr/constভ্যারিয়েবল internal linkage পায়, প্রতিটি ফাইলে আলাদা কপি। একটাই বস্তু চাইলেinline constexpr. -
কোনটা কোথায়? একটি
studentমডিউলের জন্য নিচের প্রতিটি জিনিসstudent.hনাstudent.cpp-তে যাবে বলুন: (১)struct Student { std::string name; int roll; };(২)double average(const Student&)-এর ২০ লাইনের দেহ (৩)inline constexpr int max_subjects = 10;(৪) নম্বর গোল করার ছোট সহকারীround_mark, যা শুধুaverageব্যবহার করে (৫)using namespace std;(৬) একটি template ফাংশনprint_all.(১) header — ব্যবহারকারীর টাইপের পুরো সংজ্ঞা লাগে। (২)
.cpp— header-এ শুধু ঘোষণা। (৩) header — সব ফাইল একই ধ্রুবক দেখবে। (৪).cpp-র anonymous namespace (বাstatic) — বাইরে দেখানোর দরকার নেই। (৫) কোথাও না হলেই ভালো; লিখতেই হলে শুধু.cpp-তে, header-এ কখনো নয়। (৬) header — template-এর পুরো কোড ব্যবহারের জায়গায় দেখা যেতে হয়। -
নাম খোঁজা অনুমান করুন: নিচের কোডে
show()কী ছাপাবে? তারপরnamespace app-এর ভেতরেরint level = 2;মুছে দিলে কী হবে?int level = 1; namespace app { int level = 2; namespace ui { void show() { std::cout << level; } } }প্রথমে
2— নাম খোঁজাshowথেকেapp::ui, তারপরapp-এ গিয়েlevel = 2পেয়ে থামে। মুছে দিলে খোঁজা global পর্যন্ত যায় এবং1ছাপে — কোনো error ছাড়াই। এই “নীরব বদল” এড়াতে গুরুত্বপূর্ণ জায়গায়app::levelবা::levelস্পষ্ট লিখুন, আর একই নাম বিভিন্ন স্তরে রাখা এড়িয়ে চলুন। -
AI অডিট (হাতে-কলমে): যেকোনো AI-কে বলুন: “একটি C++ ব্যাংক অ্যাকাউন্ট প্রোগ্রাম account.h, account.cpp আর main.cpp-তে ভাগ করে লেখো।” উত্তরটি অংশ ৯-এর চেকলিস্ট দিয়ে যাচাই করুন, তারপর
g++ -std=c++23 -Wall -Wextra -cদিয়ে প্রতিটি.cppআলাদা compile করে link করুন। কী কী পেলেন?প্রায়ই যা দেখা যায়:
account.h-এর শুরুতে#include <bits/stdc++.h>বাusing namespace std;.- Guard আছে, কিন্তু নাম
_ACCOUNT_H_— সংরক্ষিত নাম। - Class-এর বাইরে, header-এই
void Account::deposit(…) { … }— inline ছাড়া। এখন চলবে, কিন্তু দ্বিতীয় কোনো.cppheader include করলেইmultiple definition. যাচাই করতে একটি ফাঁকাextra.cppবানিয়ে শুধু#include "account.h"লিখে সাথে link করুন। - কোনো প্রজেক্ট-namespace নেই, সব global-এ।
অংশ ৯-এর prompt-এর নিয়মগুলো দিয়ে আবার চাইলে উত্তর সাধারণত অনেক পরিষ্কার হয় — তবু চেকলিস্ট আর আলাদা compile-এর পরীক্ষা বাদ দেবেন না।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমএকটি C++ প্রজেক্টে নতুন money মডিউল যোগ করার ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- 📋
money.hবানিয়ে প্রথম লাইনে#pragma once - 🏷️
namespace shop::money-এর ভেতরে টাইপ আর ফাংশনের ঘোষণা - 🍳
money.cpp-তে আগে#include "money.h", তারপর ফাংশনের দেহ - 🧾 যে
.cppব্যবহার করবে, সেখানে#include "money.h" - 🧵 প্রতিটি
.cppআলাদা compile করে সব.olink
C++-এ #include "money.h" লাইনটি preprocess-এর সময় আসলে কী করে?
- money.h আলাদাভাবে compile করে link করে দেয়
- money.h-এর পুরো লেখা ওই লাইনের জায়গায় বসিয়ে দেয়
- money.h-এর আগে তৈরি বাইনারি interface পড়ে নেয়
- money namespace-এর সব নাম খুলে দেয়
Include guard (#ifndef … #define … #endif) মূলত কোন সমস্যা ঠেকায়?
- দুটি .cpp ফাইলে একই ফাংশনের সংজ্ঞা থেকে linker-এর multiple definition error
- দুটি library-র একই নামের ফাংশনের সংঘাত
- একই header একটি translation unit-এ দুবার ঢুকে redefinition error হওয়া
- প্রতিটি .cpp-তে header পড়ার সময় পুরোপুরি বাঁচানো
একটি guard-যুক্ত header-এ int tax(int x) { return x / 10; } লেখা আছে (inline ছাড়া), আর দুটি .cpp ফাইল সেটা include করে। Build করলে কী হবে?
- Compile সফল, কিন্তু link-এ multiple definition error
- Compile-এর সময়েই redefinition error
- Guard থাকায় কোনো সমস্যা নেই
- Linker নীরবে একটি সংজ্ঞা রেখে দেয়
একটি .cpp ফাইলে namespace { int helper() { return 42; } } লেখার ফল কী?
- helper সব ফাইল থেকে ::helper নামে ডাকা যায়
- Compile error, কারণ namespace-এর নাম লাগবেই
- helper স্বয়ংক্রিয়ভাবে inline হয়ে ডাকার জায়গায় বসে যায়
- helper শুধু সেই ফাইলের ভেতরে দেখা যায় (internal linkage)
Library-র header ফাইলের global অংশে using namespace std; লেখা কেন বিপজ্জনক?
- প্রোগ্রাম চলার সময় ধীর হয়ে যায়
- যত ফাইল header-টি include করে, সবখানে std-এর সব নাম খুলে যায় আর তা ফেরানো যায় না
- Header-এ using লিখলে compile হয় না
- std-এর সব ফাংশন internal linkage পেয়ে যায়
C++17-এ header-এ inline int order_count = 0; লিখলে কী পাওয়া যায়?
- প্রতিটি .cpp ফাইলে আলাদা আলাদা order_count
- Compile error, কারণ শুধু ফাংশন inline হতে পারে
- Header-এ থাকলেও পুরো প্রোগ্রামে একটাই order_count, linker error ছাড়া
- order_count একটি ধ্রুবক হয়ে যায়, বদলানো যায় না
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- namespace সাজায় নাম, header/source সাজায় ফাইল; nested
shop::billing, alias, আরusing-এর প্রভাব যে scope-এ লেখা সেখানেই শেষ। - নাম খোঁজা চলে ভেতর থেকে বাইরে, প্রথম মিলেই থামে; ADL argument-এর namespace-এও খোঁজে।
#includeহলো কপি-পেস্ট; প্রতিটি header-এ#pragma onceবা guard — এটি এক translation unit-এ দুবার ঢোকা আটকায়।- ODR: সাধারণ ফাংশন/ভ্যারিয়েবলের সংজ্ঞা একবারই; header-এ দেহ রাখলে
inline(C++17 থেকে ভ্যারিয়েবলেও), নইলেmultiple definition. - ফাইলের নিজস্ব সহকারী anonymous namespace-এ; header-এ কখনো
using namespace; AI-এর ভাগ করা প্রজেক্ট প্রতিটি.cppআলাদা compile করে যাচাই।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ১২ · enum class, struct আর structured bindingsপরবর্তী পর্বনিজের টাইপ দিয়ে ডেটাকে অর্থবহ করা।
- C++ প্রোগ্রামিং · কোর্স হোম৫৫টি পর্বপুরো কোর্সের রোডম্যাপ — কোন পর্ব কখন।
- C প্রোগ্রামিং · মডুলার প্রোগ্রাম — header, Makefile, libraryগভীরে যানC-র দিক থেকে header, static/extern আর library তৈরি।
- C প্রোগ্রামিং · প্রিপ্রসেসর ও ম্যাক্রোপাশের পর্ব
#include,#defineআর#ifndef-এর পুরো যন্ত্র।