সরাসরি পর্বে যান
পর্ব ৫৩ · ৫৫-এর মধ্যে · মডিউল ৮

MCQ প্রশ্নব্যাংক — C++-এর ৮৭টি প্রশ্নে যেকোনো পরীক্ষার প্রস্তুতি

C++ MCQ Question Bank — 87 Questions, Every Option Explained

Taught in Bangla, this question bank has 87 C++ MCQs covering the whole course, from toolchain and types to pointers, OOP, templates, STL, modern C++ and concurrency. Every option is explained, with answer-selection strategies and a final score.

এই পর্বে যা করবেন

  • MCQ পরীক্ষা C++-এর কোন কোন দিক যাচাই করে, তা চেনা
  • বাদ দেওয়ার পদ্ধতি, ভুলে নম্বর কাটার হিসাব আর সময় ভাগের কৌশল শেখা
  • পুরো কোর্সের ৮৭টি প্রশ্নে নিজের দুর্বল জায়গা খুঁজে বের করা
  • সবচেয়ে প্রচলিত ফাঁদ — struct বনাম class, virtual destructor, std::move, map-এর [], vector<bool>, pointer-এ const-এর জায়গা — গভীরভাবে বোঝা
  • AI দিয়ে আরও প্রশ্ন বানানো, আর সেগুলোর “সঠিক উত্তর” compile করে যাচাই করা

১ · MCQ পরীক্ষা C++-কে কীভাবে যাচাই করে — আর উত্তর বাছাইয়ের কৌশল

নাদিয়া একটি সফটওয়্যার কোম্পানির অনলাইন পরীক্ষার জন্য পড়ছেন। একই মাসে একটি সরকারি প্রতিষ্ঠানের প্রোগ্রামার পদের লিখিত পরীক্ষাও আছে। দুই জায়গাতেই প্রথম ধাপ MCQ — চারটি অপশন, একটি সঠিক। নাদিয়া C++-এ প্রজেক্ট করেছেন, class লিখেছেন, STL ব্যবহার করেছেন। তবু প্রথম অনুশীলন সেটে ৪০টির মধ্যে ঠিক হলো ২৪টি। কেন?

কারণ MCQ প্রোগ্রাম লেখার পরীক্ষা নয়। এটি নিখুঁত খুঁটিনাটির পরীক্ষা। কোড লেখার সময় compiler ভুল ধরিয়ে দেয়, আপনি ঠিক করে নেন। MCQ-তে compiler নেই — আপনার মাথাই compiler. আর প্রশ্নকর্তা জেনেশুনে এমন ভুল অপশন রাখেন, যা দেখতে একদম ঠিক মনে হয়। এমন বিভ্রান্তিকর অপশনকে বলে distractor. C++-এ distractor বানানো আরও সহজ, কারণ ভাষাটি বড়, আর অনেক নিয়মের ব্যতিক্রম আছে।

সাত ধরনের প্রশ্ন

C++-এর MCQ মোটামুটি সাতটি ছাঁচে পড়ে। কোন ছাঁচের প্রশ্ন, সেটা চিনতে পারলে অর্ধেক কাজ হয়ে যায়:

ধরনউদাহরণআসলে কী মাপে
ধারণা মনে রাখা“RAII কী বোঝায়?”সংজ্ঞা সঠিকভাবে জানেন কি না
বৈধ কোনটি?“কোন লাইনটি compile হবে না?”ব্যাকরণ, টাইপ আর overload-এর নিয়ম
OOP-এর পরিভাষা“কোনটি abstract class?”class, inheritance, virtual-এর শব্দভান্ডার
আউটপুট কী?ছোট কোড, চারটি সম্ভাব্য আউটপুটমাথায় কোড চালানো — constructor-এর ক্রম, lambda capture
STL ও কমপ্লেক্সিটি“map::find-এর সময়?”কোন container কোন কাজে কত দ্রুত
keyword ও header“std::accumulate কোন header-এ?”স্ট্যান্ডার্ড লাইব্রেরি ও keyword-এর পরিচয়
আধুনিক C++“std::expected কোন মানদণ্ডে এলো?”C++11 থেকে C++23-এর নতুন সুবিধা

কৌশল ১ · বাদ দেওয়ার পদ্ধতি

সঠিক উত্তর সরাসরি খোঁজার চেয়ে ভুলগুলো বাদ দেওয়া প্রায়ই সহজ। একে বলে elimination method. প্রতিটি অপশনকে আলাদা করে জিজ্ঞেস করুন: “এটি ঠিক হতে হলে কোন নিয়মটি সত্য হতে হবে?” যে অপশনের পেছনের নিয়মটি মিথ্যা, সেটি কেটে দিন। নিচের ডায়াগ্রামে একটি চতুর প্রশ্ন এভাবে ধাপে ধাপে সমাধান করা হয়েছে।

  1. বিষয় চেনা{3, 5}initialization
  2. বৈধ কি?হ্যাঁঘ বাদ
  3. কয়টি উপাদান?2ক বাদ
  4. প্রথমটি কী?3গ বাদ
  5. যাচাই2 3উত্তর খ
প্রশ্নC++23, GCC
std::vector<int> v{3, 5};
std::cout << v.size() << ' ' << v[0];

ক) 3 5   খ) 2 3
গ) 2 5   ঘ) Compile error
ধাপ ২কোডটি কি তৈরি হবে?
vector<int> + {int, int}  → বৈধ
কোনো narrowing নেই, টাইপ মেলে

ক) 3 5   খ) 2 3
গ) 2 5   ঘ) Compile error
ধাপ ৩{} কোন constructor ডাকে?
{3, 5}  → initializer_list আগে পছন্দ
       → উপাদান দুটি: 3 আর 5
size() = 2

ক) 3 5   খ) 2 3
গ) 2 5   ঘ) Compile error
ধাপ ৪v[0] কোনটি?
v = [ 3, 5 ]
v[0] = প্রথম উপাদান = 3

ক) 3 5   খ) 2 3
গ) 2 5   ঘ) Compile error
ধাপ ৫উত্তর ও ফাঁদের চেক
উত্তর: খ) 2 3

যদি লেখা থাকত  std::vector<int> v(3, 5);
তাহলে ৩টি 5 → উত্তর হতো ক) 3 5
  1. আগে বিষয় চিনুন। কোডে কোনো লুপ বা হিসাব নেই — আছে শুধু একটি vector বানানোর লাইন। তাই এটি “initialization” ধরনের প্রশ্ন। মাথায় প্রশ্ন আনুন: বন্ধনী {} হলে কোন constructor ডাকা হয়, আর () হলে কোনটি?
  2. কোডটি কি আদৌ তৈরি হবে? int-এর vector-এ দুটি int দেওয়া হয়েছে — টাইপ মেলে, কোনো narrowing conversion নেই। তাই “Compile error” (ঘ) বাদ। এই প্রশ্ন সব সময় আগে করুন, কারণ প্রশ্নকর্তা প্রায়ই সঠিক উত্তর হিসেবেই এই অপশন রাখেন।
  3. কয়টি উপাদান? std::vector-এর একটি constructor নেয় std::initializer_list. বন্ধনী {} দিয়ে লিখলে এই constructor আগে বিবেচনা হয়, যদি টাইপ মেলে। তাই {3, 5} মানে “৩ আর ৫ — এই দুটি উপাদান”, “৫-এর ৩টি কপি” নয়। size() = ২, তাই ক বাদ।
  4. প্রথম উপাদান কোনটি? উপাদান দুটি ক্রমে বসে: ৩, তারপর ৫. v[0] প্রথমটি — ৩. তাই গ (2 5) বাদ। এখন বাকি একটিই।
  5. উত্তর খ — তারপর ফাঁদটি মনে রাখুন। শেষে ভাবুন, প্রশ্নকর্তা ক কেন রেখেছিলেন। গোল বন্ধনী v(3, 5) হলে count-value constructor ডাকা হতো — ৩টি উপাদান, সবগুলো ৫ — আর উত্তর হতো ক. ভুল অপশন দেখেই বোঝা যায় প্রশ্নটি কোন ভুল ধারণা ধরতে চায়।

কৌশল ২ · “Compile error”, “UB” আর “Implementation-defined” অপশন

C++-এর MCQ-তে প্রায়ই একটি অপশন থাকে “Compile error” বা “Undefined behavior”. অনেকে এগুলো “ফাঁকা ঘর” ভেবে এড়িয়ে যান। ভুল। এগুলো প্রায়ই সঠিক উত্তর। তিনটির পার্থক্য পরিষ্কার রাখুন:

Compile error

কোডটি ভাষার নিয়ম ভাঙে, তাই প্রোগ্রামই তৈরি হয় না। যেমন unique_ptr কপি করা, int x{3.5};, বা abstract class-এর object বানানো।

প্রোগ্রাম তৈরিই হয় না

Undefined behavior

কোড compile হয়, কিন্তু মানদণ্ড ফলের কোনো দায় নেয় না। যেমন signed overflow, dangling reference, new[]-এর পরে শুধু delete. এগুলো undefined behavior.

কোনো ফলই নিশ্চিত নয়

Implementation-defined

ফল নির্দিষ্ট, কিন্তু compiler ও প্ল্যাটফর্ম ঠিক করে এবং লিখে জানায়। যেমন sizeof(long), sizeof(bool). এটি implementation-defined behavior.

প্ল্যাটফর্ম জানলে উত্তর জানা যায়
মূল ধারণা

প্রশ্নে কোড থাকলে আউটপুট খোঁজার আগে দুটি প্রশ্ন করুন। এক, “এটি কি compile হবে?” — কপি নিষেধ এমন টাইপ কপি হচ্ছে কি, const বদলানো হচ্ছে কি, overload দ্ব্যর্থ কি? দুই, “compile হলে কি UB?” — মরে যাওয়া object-এর reference, সীমার বাইরে পড়া, signed overflow, দুই thread-এ একসাথে একই ভ্যারিয়েবল লেখা? দুটির কোনোটির উত্তর “হ্যাঁ” হলে সংখ্যা নিয়ে মাথা ঘামানোর দরকার নেই।

কৌশল ৩ · ভুলে নম্বর কাটলে কখন আন্দাজ করবেন

যেসব পরীক্ষায় ভুল উত্তরে নম্বর কাটে (negative marking), সেখানে আন্দাজ করা একটি হিসাবের প্রশ্ন। নিয়ম পরীক্ষাভেদে আলাদা, তাই নিজের পরীক্ষার নিয়ম আগে জেনে নিন। এখানে শুধু হিসাবের পদ্ধতিটি দেখাচ্ছি। ধরুন সঠিক উত্তরে ১ নম্বর। প্রতি প্রশ্নে গড়ে কত পাবেন:

বাকি অপশনভুলে ০.২৫ কাটলেভুলে ০.৫ কাটলে
৪টি (কিছুই জানেন না)+০.০৬−০.১২৫ (ক্ষতি)
৩টি (একটি বাদ দিয়েছেন)+০.১৭০ (লাভ-ক্ষতি নেই)
২টি (দুটি বাদ দিয়েছেন)+০.৩৭৫+০.২৫

হিসাবটি সহজ: (ঠিক হওয়ার সম্ভাবনা × ১) − (ভুল হওয়ার সম্ভাবনা × কাটা নম্বর)। যেমন দুটি অপশন বাকি আর ০.৫ কাটা: ০.৫ − ০.৫ × ০.৫ = ০.২৫. শিক্ষা পরিষ্কার: অন্তত দুটি অপশন নিশ্চিতভাবে বাদ দিতে পারলে আন্দাজ সাধারণত লাভজনক; কিছুই না জানলে আর কাটা নম্বর বেশি হলে ছেড়ে দিন। কিন্তু “নিশ্চিতভাবে” কথাটিই আসল — ভুল কারণে বাদ দিলে হিসাব উল্টে যায়।

কৌশল ৪ · সময় ভাগ করা

ধরুন ১০০টি প্রশ্ন ৬০ মিনিটে — প্রতিটির জন্য গড়ে ৩৬ সেকেন্ড। একটি constructor-destructor-এর ক্রমের প্রশ্নেই দুই মিনিট চলে যেতে পারে। তাই দুই দফায় চলুন। প্রথম দফায় সংজ্ঞা, header, কমপ্লেক্সিটি, keyword-এর মতো সরাসরি প্রশ্ন দ্রুত শেষ করুন। কোড-ভারী প্রশ্নে দাগ দিয়ে এগিয়ে যান। দ্বিতীয় দফায় ফিরে এসে সেগুলো ধরুন। এক প্রশ্নে আটকে থেকে শেষের পাঁচটি সহজ প্রশ্ন না দেখা — MCQ পরীক্ষায় এটিই সবচেয়ে দামি ভুল।

এই পাতার প্রশ্নগুলো সম্পর্কে: প্রশ্নগুলো এ ধরনের পরীক্ষায় প্রায়ই আসে এমন ধারণা নিয়ে বানানো — কোনোটিই কোনো নির্দিষ্ট পরীক্ষার আসল প্রশ্ন নয়। প্রতিটি আউটপুট, আকার আর compile error GCC দিয়ে C++23 মোডে, ৬৪-বিট Linux-এ যাচাই করা। প্রশ্নের শুরুতে কঠিনতার মাত্রা দেওয়া আছে: (সহজ), (মাঝারি), (কঠিন)।

C জানলে

C-এর MCQ-র বড় অংশ — টুলচেইনের ধাপ, integer division, pointer arithmetic, signed overflow-এর UB — C++-এও হুবহু একই। কিন্তু কয়েকটি উত্তর বদলে যায়: C++-এ sizeof('A') হলো ১ (C-তে ৪), const int একটি constant expression, আর bool একটি আসল টাইপ। C-এর একই ধাঁচের ৮০টি প্রশ্ন আছে C কোর্সের MCQ প্রশ্নব্যাংক পর্বে; যিনি C শেখেননি, তিনি এই বাক্সটি বাদ দিতে পারেন।

২ · ভিত্তি, টুলচেইন আর টাইপ

এই অংশের প্রশ্ন সহজ মনে হয়, তাই সবাই তাড়াহুড়া করেন — আর ঠিক সেখানেই ভুল হয়। সবচেয়ে বেশি আসে: C++-এর ইতিহাস, কোন error কোন ধাপের, auto কোন টাইপ ধরে, আর brace initialization-এর narrowing নিষেধ।

(সহজ) C++ ভাষা মূলত কে তৈরি করেন?

  • Dennis Ritchie
  • Bjarne Stroustrup
  • James Gosling
  • Guido van Rossum

(সহজ) C++-এর প্রথম আন্তর্জাতিক (ISO) মানদণ্ড কোনটি?

  • C++98
  • C++03
  • C++11
  • C with Classes

(মাঝারি) Build করার সময় বার্তা এলো undefined reference to `area(double)'. এটি কোন ধাপের error?

  • Preprocessor
  • Compiler
  • Runtime
  • Linker

(মাঝারি) C++ compiler ফাংশনের নাম mangle করে (যেমন _Z4aread) মূলত কেন?

  • কোড চুরি ঠেকাতে নাম লুকানো
  • একই নামের ভিন্ন ফাংশন (overload) linker-এর কাছে আলাদা করতে
  • link করা দ্রুত করতে
  • executable-এর আকার ছোট করতে

(সহজ) auto x = 5u; — x-এর টাইপ কী?

  • int
  • unsigned int
  • unsigned long
  • Compile error

(মাঝারি) int n = 7; const int& r = n; auto a = r; — a-এর টাইপ কী?

  • const int&
  • const int
  • int
  • int&

(মাঝারি) int x{3.5}; লাইনটির কী হয়?

  • x = ৩, চুপচাপ
  • x = ৪, গোল করে
  • Undefined behavior
  • Compile error — narrowing

(মাঝারি) C++ মানদণ্ড কোন আকারটি সব প্ল্যাটফর্মে নিশ্চিত করে?

  • sizeof(int) == 4
  • sizeof(char) == 1
  • sizeof(long) == 8
  • sizeof(bool) == 1

(কঠিন) C++-এ sizeof('A')-এর মান কত?

  • ১
  • ২
  • ৪
  • ৮

(মাঝারি) int x = 1; থাকলে কোন ঘোষণায় *p = 5; চলবে, কিন্তু p = &y; compile হবে না?

  • const int* p = &x;
  • int const* p = &x;
  • int* const p = &x;
  • const int* const p = &x;

৩ · অপারেটর, I/O, নিয়ন্ত্রণ, ফাংশন আর reference

এখানে ছোট একটি লাইনে অনেক নিয়ম লুকিয়ে থাকে — integer division, তুলনার শিকল, short-circuit, switch-এর fall-through, আর overload resolution. Reference-এর প্রশ্নে সবচেয়ে প্রচলিত ভুল: ভাবা যে reference পরে অন্য ভ্যারিয়েবলে “সরে” যায়।

(সহজ) কী ছাপা হবে?

std::cout << -7 / 2 << ' ' << -7 % 2;
  • -4 1
  • -3 -1
  • -3 1
  • -4 -1

(মাঝারি) int i = 0; bool b = (i != 0) && (10 / i > 1); — কী ঘটে?

  • শূন্য দিয়ে ভাগ — undefined behavior
  • b = true
  • b = false, ভাগটি কখনো হয় না
  • Compile error

(সহজ) std::endl আর '\n'-এর পার্থক্য কী?

  • std::endl নতুন লাইনের সাথে stream flush-ও করে
  • std::endl Windows-এ দুটি অক্ষর লেখে, '\n' একটি
  • '\n' শুধু C-তে চলে, C++-এ নয়
  • কোনো পার্থক্য নেই

(মাঝারি) x = 2 হলে কী ছাপা হবে? (কোনো case-এ break নেই)

switch (x) {
  case 1: std::cout << "a";
  case 2: std::cout << "b";
  case 3: std::cout << "c";
  default: std::cout << "d";
}
  • b
  • bc
  • bcd
  • abcd

(মাঝারি) কোন জোড়া ঘোষণা একসাথে একই scope-এ রাখা যায় না?

  • void f(int); আর void f(double);
  • void f(int); আর void f(int, int);
  • int f(int); আর double f(int);
  • void f(int&); আর void f(const int&);

(মাঝারি) void f(int); আর void f(double); দুটিই আছে। f('a') কোনটি ডাকে?

  • f(int)
  • f(double)
  • Ambiguous — compile error
  • কোনোটিই নয় — compile error

(মাঝারি) শেষে a আর b-এর মান কত?

int a = 1, b = 2;
int& r = a;
r = b;
r = 5;
  • a = 2, b = 5
  • a = 5, b = 2
  • a = 2, b = 2
  • Compile error — reference আবার বাঁধা যায় না

(মাঝারি) std::vector<int> v{1, 2, 3}; for (auto x : v) x *= 2; — পরে v-তে কী থাকে?

  • 2 4 6
  • অপরিবর্তিত — 1 2 3
  • Undefined behavior
  • Compile error

(মাঝারি) void f(int a, int b = 2, int c); — এই ঘোষণার কী হয়?

  • বৈধ; f(1) ডাকা যাবে
  • বৈধ; c নিজে থেকে ০ হয়
  • Compile error — ডিফল্ট আর্গুমেন্ট শেষের দিকে থাকতে হয়
  • Linker error

(মাঝারি) enum class Color { Red, Green }; int x = Color::Red; — কী হয়?

  • Compile error — নিজে থেকে int-এ রূপান্তর হয় না
  • x = 0
  • x = 1
  • Undefined behavior

(সহজ) header ফাইলে using namespace std; লেখাকে খারাপ অভ্যাস বলা হয় কেন?

  • প্রোগ্রাম চলার সময় ধীর হয়ে যায়
  • header-এ এটি compile error
  • যে ফাইল এটি include করবে, তাতেও std-এর সব নাম ঢুকে নামের সংঘর্ষ ঘটাতে পারে
  • এতে std namespace মুছে যায়

৪ · vector, string, pointer, মেমরি আর lifetime

এই অংশের প্রশ্নের একটি বড় ভাগের উত্তর “undefined behavior”. প্রশ্নকর্তা দেখতে চান, আপনি জানেন কি না কোন object কখন মরে, কোন pointer বা reference তখন ঝুলে থাকে (dangling), আর কে কোন মেমরির মালিক (ownership).

(সহজ) std::vector<int> v; v.reserve(100); — এরপর v.size() কত?

  • ১০০
  • ০
  • অনির্দিষ্ট — implementation ঠিক করে
  • ১

(মাঝারি) v-তে ৩টি উপাদান। v.at(10) আর v[10] সম্পর্কে কোনটি সঠিক?

  • দুটিই std::out_of_range ছোড়ে
  • [] exception ছোড়ে, at() UB
  • at() std::out_of_range ছোড়ে, [] undefined behavior
  • দুটিই vector-কে ১১টি উপাদানে বাড়িয়ে নেয়

(মাঝারি) ৬৪-বিট Linux-এ GCC-র libstdc++-এ sizeof(std::string) কত?

  • ৮
  • ২৪
  • ৩২
  • লেখার দৈর্ঘ্যের ওপর নির্ভর করে

(কঠিন) std::string_view sv = std::string("Dhaka") + "!"; — এরপর std::cout << sv; লিখলে?

  • Dhaka! নিশ্চিতভাবে
  • Compile error
  • খালি লাইন
  • Undefined behavior — sv মরে যাওয়া temporary দেখায়

(মাঝারি) NULL-এর বদলে nullptr কেন ভালো?

  • nullptr কোনো integer নয়, তাই overload-এ ভুল ফাংশন ডাকা হয় না
  • nullptr dereference করলে crash হয় না
  • nullptr মেমরিতে সব সময় ঠিকানা ০
  • NULL শুধু C-তে আছে, C++-এ নেই

(মাঝারি) int* p = new int[10]; delete p; — কী হয়?

  • ঠিকঠাক — পুরো array মুক্ত হয়
  • Compile error
  • Undefined behavior — new[]-এর সাথে delete[] লাগে
  • শুধু প্রথম উপাদানটি মুক্ত হয়, বাকি ৯টি leak

(মাঝারি) auto p = std::make_unique<int>(5); auto q = p; — কী হয়?

  • p আর q দুজনেই একই int-এর মালিক
  • Compile error — unique_ptr কপি করা যায় না
  • মালিকানা নিজে থেকে q-তে সরে, p খালি হয়
  • Double delete — undefined behavior

(মাঝারি) শেষ লাইনে কী ছাপা হবে?

auto a = std::make_shared<int>(1);
auto b = a;
{ auto c = a; }
std::cout << a.use_count();
  • ১
  • ৩
  • ২
  • ০

(কঠিন) দুটি object একে অপরকে std::shared_ptr দিয়ে ধরে রাখে। বাইরের সব pointer চলে গেলে কী হয়, আর সমাধান কী?

  • দুটিই ঠিকঠাক মুক্ত হয়; সমস্যা নেই
  • Crash হয়; সমাধান unique_ptr
  • Compile error; সমাধান auto
  • Memory leak; সমাধান একটি দিকে std::weak_ptr

(মাঝারি) const int& f() { int x = 5; return x; } — int y = f(); লিখলে?

  • Undefined behavior — dangling reference
  • নিশ্চিতভাবে y = 5
  • Compile error
  • x-এর আয়ু বেড়ে y পর্যন্ত থাকে

(মাঝারি) RAII-এর মূল নিশ্চয়তা কোনটি?

  • একটি garbage collector সময়মতো মেমরি মুক্ত করে
  • object scope ছাড়লেই destructor resource ছেড়ে দেয় — exception এলেও
  • প্রোগ্রামে কখনো exception আসে না
  • প্রতিটি new-এর পরে প্রোগ্রামার নিজে delete ডাকেন

এবার দুটি অংশের আকারের দাবিগুলো নিজের চোখে দেখুন। কোডটি চালান — এতে কোনো ফাঁদ নেই, শুধু তথ্য:

#include <memory>
#include <print>
#include <string>
#include <vector>

struct Empty {};
struct Poly { virtual void f() {} virtual ~Poly() = default; };

int main() {
    std::println("char={} 'A'={} bool={} int={} long={} ptr={}",
                 sizeof(char), sizeof('A'), sizeof(bool),
                 sizeof(int), sizeof(long), sizeof(void*));
    std::println("string={} vector={} unique_ptr={} shared_ptr={}",
                 sizeof(std::string), sizeof(std::vector<int>),
                 sizeof(std::unique_ptr<int>), sizeof(std::shared_ptr<int>));
    std::println("Empty={} Poly={}", sizeof(Empty), sizeof(Poly));
    std::println("-7/2={} -7%2={}", -7 / 2, -7 % 2);
}

৬৪-বিট Linux-এ GCC ও libstdc++-এ ফল: char=1 'A'=1 bool=1 int=4 long=8 ptr=8, তারপর string=32 vector=24 unique_ptr=8 shared_ptr=16, Empty=1 Poly=8, আর শেষে -7/2=-3 -7%2=-1. লক্ষ করুন: std::vector তিনটি pointer (শুরু, শেষ, জায়গার শেষ) — ২৪; shared_ptr দুটি pointer (object আর control block) — ১৬; virtual ফাংশনওয়ালা class একটি লুকানো vptr রাখে — ৮. মানদণ্ড নিজে শুধু sizeof(char) = ১ আর খালি class-এর আকার শূন্য নয় — এটুকু নিশ্চিত করে; বাকিগুলো প্ল্যাটফর্ম ও লাইব্রেরির সিদ্ধান্ত। ৬৪-বিট Windows-এ long ৪ হবে, Clang-এর libc++-এ string ২৪.

৫ · OOP — class, constructor, inheritance আর virtual

OOP-এর প্রশ্ন দুই ধরনের। এক ধরনের প্রশ্ন পরিভাষা চায় — encapsulation, abstract class, SOLID. আরেক ধরনের প্রশ্ন ছোট কোড দিয়ে যাচাই করে, compiler কোন special member function নিজে বানায়, constructor কোন ক্রমে চলে, আর কোন ডাক dynamic binding-এ যায়। দ্বিতীয় ধরনটিতেই বেশি ভুল হয়।

(সহজ) class-এর ভেতরে কোনো access specifier না লিখলে সদস্যরা কী হয়?

  • public
  • protected
  • private
  • Compile error

(মাঝারি) struct A { A(int) {} }; A a; — কী হয়?

  • compiler ডিফল্ট constructor বানিয়ে a তৈরি করে
  • Compile error — ডিফল্ট constructor নেই
  • A(0) ডাকা হয়
  • Undefined behavior

(সহজ) Rule of Three অনুযায়ী কোন তিনটির একটি নিজে লিখলে বাকি দুটিও লেখা উচিত?

  • destructor, copy constructor, copy assignment
  • ডিফল্ট constructor, destructor, copy constructor
  • move constructor, move assignment, destructor
  • operator==, operator<, operator<<

(কঠিন) একটি class-এ শুধু destructor নিজে লেখা হয়েছে (~T() {})। তখন move constructor-এর কী হয়?

  • compiler স্বাভাবিকভাবে move constructor বানায়
  • rvalue দিলে compile error
  • copy আর move দুটিই নিষেধ হয়ে যায়
  • move constructor তৈরি হয় না; “move” আসলে কপি হয়ে যায়

(মাঝারি) D d; একটি block-এর ভেতরে তৈরি হয়ে block শেষে ধ্বংস হলো। কী ছাপা হবে?

struct B { B() { std::cout << "B"; }
           ~B() { std::cout << "b"; } };
struct D : B { D() { std::cout << "D"; }
              ~D() { std::cout << "d"; } };
  • DBdb
  • BDbd
  • BDdb
  • Dd

(কঠিন) struct S { int a; int b; S() : b(1), a(b + 1) {} }; — S s;-এর পরে s.a কত?

  • ২
  • Undefined behavior — a তৈরির সময় b-তে মান বসেনি
  • ১
  • Compile error

(মাঝারি) কোন অপারেটরটি C++-এ overload করা যায় না?

  • []
  • ()
  • ::
  • <<

(মাঝারি) Base-এ void f(); (virtual নয়) আর virtual void g();; Der দুটিই override/hide করেছে। Base* p = new Der; p->f(); p->g(); — কোনগুলো চলে?

  • Der::f, Der::g
  • Base::f, Der::g
  • Base::f, Base::g
  • Der::f, Base::g

(কঠিন) Base-এর destructor virtual নয়। Base* p = new Derived; delete p; — কী হয়?

  • প্রথমে ~Derived, তারপর ~Base — স্বাভাবিক
  • Compile error
  • শুধু ~Base চলে — নিশ্চিতভাবে, এটি নির্ধারিত আচরণ
  • Undefined behavior

(মাঝারি) g() virtual এবং Der-এ override করা। Der d; Base b = d; b.g(); — কোনটি চলে, কেন?

  • Base::g — object slicing
  • Der::g — virtual বলে
  • Compile error
  • Undefined behavior

(মাঝারি) struct Shape { virtual double area() const = 0; }; — Shape s; লিখলে?

  • s তৈরি হয়, area() ০ দেয়
  • চলার সময় “pure virtual call” crash
  • Compile error — abstract class-এর object বানানো যায় না
  • = 0 লেখাটিই ব্যাকরণের ভুল

(সহজ) ফাংশনের শেষে override লেখার আসল সুবিধা কী?

  • virtual ডাক দ্রুত হয়
  • base class-এ মেলানো virtual ফাংশন না থাকলে compiler error দেয়
  • derived class-এ আর override করা যায় না
  • base-এর ফাংশনকে virtual বানিয়ে দেয়

(মাঝারি) ৬৪-বিট GCC-তে struct E {}; আর struct V { virtual void f(); }; — sizeof(E) আর sizeof(V) কত?

  • ০ আর ০
  • ১ আর ১
  • ৮ আর ৮
  • ১ আর ৮

(মাঝারি) SOLID-এর কোন নীতি বলে, base class যেখানে চলে, derived class-এর object সেখানে বসালেও প্রোগ্রাম ঠিক থাকতে হবে?

  • Single Responsibility
  • Open/Closed
  • Liskov Substitution
  • Dependency Inversion

৬ · template আর STL container

STL-এর প্রশ্নের অর্ধেক কমপ্লেক্সিটি — কোন container-এ কোন কাজ কত দ্রুত (Big-O). বাকি অর্ধেক template-এর নিয়ম: টাইপ কীভাবে অনুমান হয় (template argument deduction), আর কোন কাজের পরে iterator অচল হয়ে যায় (iterator invalidation).

(মাঝারি) template<typename T> T maxv(T a, T b); — maxv(3, 4.5) ডাকলে?

  • T = double, ফল ৪.৫
  • Compile error — T-এর দুটি ভিন্ন অনুমান
  • T = int, ফল ৪
  • Undefined behavior

(মাঝারি) template-এর পুরো সংজ্ঞা সাধারণত header ফাইলে রাখা হয় কেন?

  • header-এ রাখলে প্রোগ্রাম দ্রুত চলে
  • মানদণ্ড .cpp ফাইলে template নিষেধ করে
  • linker template নিজে instantiate করে
  • প্রতিটি ব্যবহারের জায়গায় compiler-এর পুরো সংজ্ঞা লাগে, instantiate করতে

(সহজ) std::map-এ find()-এর সময় কত?

  • O(log n)
  • O(1)
  • O(n)
  • O(n log n)

(মাঝারি) std::unordered_map-এ খোঁজার সময় কত?

  • সব সময় O(1)
  • সব সময় O(log n)
  • গড়ে O(1), সবচেয়ে খারাপ ক্ষেত্রে O(n)
  • O(n log n)

(মাঝারি) কোন container দুই প্রান্তেই দ্রুত (constant time) যোগ করতে দেয়, আবার [] দিয়ে সূচকেও পড়তে দেয়?

  • std::vector
  • std::deque
  • std::list
  • std::forward_list

(মাঝারি) std::list<int> l{3, 1, 2}; std::sort(l.begin(), l.end()); — কী হয়?

  • l হয় 1 2 3
  • চলার সময় crash
  • Undefined behavior
  • Compile error — list-এর iterator random access নয়

(কঠিন) auto it = v.begin(); v.push_back(4); std::cout << *it; — v একটি std::vector<int>. কোন কথাটি সঠিক?

  • সব সময় প্রথম উপাদান ছাপে — নিরাপদ
  • Compile error
  • reallocation হলে it অচল — পড়া undefined behavior
  • it নিজে থেকে নতুন প্রথম উপাদানে চলে যায়

(মাঝারি) std::map<std::string, int> m; if (m["x"] == 0) {} — এরপর m.size() কত?

  • ০
  • ১
  • ২
  • Compile error

(সহজ) std::set<int> s{3, 1, 3, 2}; for (int x : s) std::cout << x; — কী ছাপা হবে?

  • 123
  • 3132
  • 1233
  • 312

(কঠিন) std::vector<bool> vb{true}; bool& r = vb[0]; — কী হয়?

  • r প্রথম উপাদানের reference হয়
  • r-এ true-এর একটি কপি থাকে
  • Undefined behavior
  • Compile error — vb[0] একটি proxy ফেরত দেয়, bool& নয়

(মাঝারি) std::priority_queue<int> pq;-এ ৩, ১, ৪ push করলে pq.top() কত?

  • ৩ — প্রথমে ঢোকানোটি
  • ১ — সবচেয়ে ছোটটি
  • ৪ — সবচেয়ে বড়টি
  • ১ — শেষে ঢোকানোটি

৭ · algorithm, lambda আর ranges

এখানে তিনটি ফাঁদ ঘুরেফিরে আসে: কোন algorithm কোন header-এ, std::remove আসলে কিছু মোছে কি না, আর lambda কোন মুহূর্তে ভ্যারিয়েবলের মান ধরে (capture). Ranges-এর প্রশ্নে মূল কথা একটিই — view অলস (lazy).

(সহজ) std::accumulate কোন header-এ ঘোষিত?

  • <algorithm>
  • <functional>
  • <numeric>
  • <cmath>

(মাঝারি) সমান key-ওয়ালা উপাদানের আগের ক্রম রেখে সাজাতে চান। কোনটি এটি নিশ্চিত করে?

  • std::sort
  • std::stable_sort
  • std::partial_sort
  • std::nth_element

(মাঝারি) সাজানো v = {1, 3, 3, 5}. std::upper_bound(v.begin(), v.end(), 3) - v.begin() কত?

  • ০
  • ১
  • ২
  • ৩

(কঠিন) std::vector<int> v{1, 2, 1, 3}; std::remove(v.begin(), v.end(), 1); — এরপর v.size() কত?

  • ৪
  • ২
  • ৩
  • Compile error

(মাঝারি) কী ছাপা হবে?

int x = 1;
auto byVal = [x] { return x; };
auto byRef = [&x] { return x; };
x = 5;
std::cout << byVal() << byRef();
  • 55
  • 11
  • 15
  • 51

(মাঝারি) int x = 1; auto f = [x] { x++; }; — কী হয়?

  • বাইরের x বাড়ে
  • Compile error — value capture ডিফল্টে read-only
  • শুধু lambda-র কপি বাড়ে, বাইরের x নয়
  • Undefined behavior

(মাঝারি) for (int i : std::views::iota(1, 5)) std::cout << i; — কী ছাপা হবে?

  • 1234
  • 12345
  • 01234
  • Compile error

(মাঝারি) auto r = v | std::views::filter(isEven) | std::views::transform(sq); — isEven কখন ডাকা হয়?

  • এই লাইনেই, সব উপাদানের জন্য একবার করে
  • compile-এর সময়
  • একটি নতুন vector তৈরির সময়, তার পরে আর নয়
  • যখন r-এর ওপর লুপ চালিয়ে উপাদান পড়া হয়

(সহজ) std::vector<int> v{1, 2, 3, 4, 6};-এ std::count_if(v.begin(), v.end(), [](int k) { return k % 2 == 0; }) কত?

  • ২
  • ৩
  • ৫
  • ১২

অংশ ৫ থেকে ৭-এর কয়েকটি আউটপুট-দাবি একসাথে চালিয়ে দেখুন — constructor-এর ক্রম, map-এর [], std::remove আর lambda capture:

#include <algorithm>
#include <iostream>
#include <map>
#include <string>
#include <vector>

struct B { B() { std::cout << "B"; } ~B() { std::cout << "b"; } };
struct D : B { D() { std::cout << "D"; } ~D() { std::cout << "d"; } };

int main() {
    { D d; }
    std::map<std::string, int> m;
    bool zero = (m["x"] == 0);
    std::vector<int> v{1, 2, 1, 3};
    auto newEnd = std::remove(v.begin(), v.end(), 1);
    int x = 1;
    auto byVal = [x] { return x; };
    auto byRef = [&x] { return x; };
    x = 5;
    std::cout << " | " << zero << m.size()
              << " | " << v.size() << (newEnd - v.begin())
              << " | " << byVal() << byRef() << '\n';
}

ফল: BDdb | 11 | 42 | 15. প্রথম অংশে base আগে তৈরি, পরে ধ্বংস। দ্বিতীয় অংশে তুলনা সত্য (১), কিন্তু শুধু পড়ার চেষ্টাতেই map-এ একটি উপাদান ঢুকে গেছে (size ১)। তৃতীয় অংশে std::remove-এর পরেও size ৪, শুধু নতুন শেষ সূচক ২-এ — বাকি দুটি ঘরে কী আছে, তা unspecified. শেষ অংশে value capture পুরোনো মান ১, reference capture নতুন মান ৫ দেখেছে। এখানে <<-এর শিকল বাঁ থেকে ডানে হিসাব হয় — C++17 থেকে এই ক্রম নিশ্চিত।

৮ · আধুনিক C++ — move, exception, optional থেকে concepts আর UB

C++11 থেকে C++23 পর্যন্ত যোগ হওয়া সুবিধা নিয়ে প্রশ্ন আসে দুইভাবে: “কোন মানদণ্ডে/কোন header-এ?” আর “এটি আসলে কী করে?” দ্বিতীয়টির সবচেয়ে বিখ্যাত উদাহরণ std::move — নাম দেখে যা মনে হয়, কাজ তা নয়।

(মাঝারি) std::move(x) নিজে আসলে কী করে?

  • x-এর ডেটা নতুন জায়গায় সরিয়ে দেয়
  • x-কে rvalue হিসেবে cast করে — সরানোর কাজ করে move constructor/assignment
  • x-কে খালি করে দেয়
  • x-এর মেমরি মুক্ত করে

(কঠিন) const std::string s = "hello"; std::string t = std::move(s); — কী ঘটে?

  • নীরবে কপি হয় — s অক্ষত থাকে
  • s থেকে move হয়, s খালি হয়
  • Compile error
  • Undefined behavior — const object বদলানো

(কঠিন) নিজের টাইপের move constructor-কে noexcept চিহ্নিত করা std::vector-এর জন্য কেন গুরুত্বপূর্ণ?

  • noexcept ছাড়া vector-এ রাখাই যায় না
  • noexcept দিলে move constructor-এর ভেতরের কোড দ্রুত চলে
  • noexcept থাকলে vector নিজে exception ধরে
  • reallocation-এ vector শুধু তখনই move করে, নয়তো কপি করে

(মাঝারি) C++11 থেকে একটি destructor-এর ভেতর থেকে exception বের হলে (বিশেষ কিছু না লিখলে) কী হয়?

  • exception স্বাভাবিকভাবে caller-এর catch-এ যায়
  • std::terminate — প্রোগ্রাম থেমে যায়
  • Compile error
  • exception নীরবে উপেক্ষা করা হয়

(মাঝারি) std::optional<int> o; (খালি) — o.value() আর *o সম্পর্কে কোনটি সঠিক?

  • দুটিই ০ দেয়
  • দুটিই exception ছোড়ে
  • value() std::bad_optional_access ছোড়ে; *o undefined behavior
  • দুটিই compile error

(সহজ) std::expected কোন মানদণ্ডে, কোন header-এ এসেছে?

  • C++23, <expected>
  • C++17, <optional>
  • C++20, <utility>
  • C++11, <exception>

(মাঝারি) std::variant<int, std::string> v = 5; std::get<std::string>(v); — কী হয়?

  • "5" ফেরত দেয়
  • খালি string ফেরত দেয়
  • Compile error
  • std::bad_variant_access ছোড়ে

(কঠিন) consteval int sq(int n) { return n * n; } — int k = 3; sq(k); লিখলে?

  • চলার সময় হিসাব হয়ে ৯
  • Compile error — ডাকটি constant expression নয়
  • Undefined behavior
  • compiler k-কে ৩ ধরে compile-এর সময়েই ৯ বসায়

(মাঝারি) template<std::integral T> T twice(T x); — twice(2.5) ডাকলে?

  • 2.5 থেকে int-এ রূপান্তর করে ৪ দেয়
  • ৫.০ দেয়
  • Compile error — constraint সন্তুষ্ট হয়নি
  • চলার সময় exception

(মাঝারি) INT_MAX ২১৪৭৪৮৩৬৪৭ ধরে, কোনটি undefined behavior?

  • unsigned u = 0; u - 1;
  • long long y = INT_MAX; y + 1;
  • int z = 100; z * 2;
  • int x = INT_MAX; x + 1;

(মাঝারি) তিন-মুখী তুলনা অপারেটর <=> (spaceship) কোন মানদণ্ডে এসেছে?

  • C++11
  • C++17
  • C++20
  • C++23

৯ · সিস্টেম ও প্রজেক্ট — ফাইল, thread, CMake, টেস্টিং, পারফরম্যান্স

শেষ অংশে ব্যবহারিক প্রশ্ন: ফাইল খোলার ডিফল্ট আচরণ, thread-এর জীবনচক্র, data race, deadlock, CMake-এর মূল কমান্ড, sanitizer-এর flag, আর কনটেস্টে সময় বাঁচানোর কৌশল। চাকরির পরীক্ষায় এখান থেকে সাধারণত ধারণার প্রশ্ন আসে, কোডের নয়।

(সহজ) আগে থেকে লেখা আছে এমন log.txt-এ std::ofstream f("log.txt"); খুললে কী হয়?

  • নতুন লেখা পুরোনোর শেষে যোগ হয়
  • পুরোনো লেখা মুছে যায় (truncate)
  • ফাইল খোলাই ব্যর্থ হয়
  • ফাইলটি শুধু পড়ার জন্য খোলে

(মাঝারি) একটি std::thread object, যাতে join() বা detach() কোনোটিই ডাকা হয়নি, scope ছেড়ে ধ্বংস হলে কী হয়?

  • std::terminate — প্রোগ্রাম থেমে যায়
  • destructor নিজে join করে অপেক্ষা করে
  • thread নিজে থেকে detach হয়ে চলতে থাকে
  • Compile error

(মাঝারি) দুটি thread কোনো lock ছাড়া একই int counter-এ ১০ লাখবার করে ++counter করে। ফল সম্পর্কে কোনটি সঠিক?

  • সব সময় ঠিক ২০ লাখ
  • সব সময় ২০ লাখের কম — নির্ধারিত আচরণ
  • Compile error
  • Data race — undefined behavior

(কঠিন) Thread ১ আগে m1, পরে m2 lock করে; thread ২ আগে m2, পরে m1. কী বিপদ, আর C++17-এর সহজ সমাধান কী?

  • Data race; সমাধান volatile
  • Spurious wakeup; সমাধান predicate
  • Deadlock; সমাধান std::scoped_lock(m1, m2)
  • Memory leak; সমাধান std::atomic

(মাঝারি) std::condition_variable-এর wait সাধারণত একটি শর্ত (predicate) দিয়ে ডাকা হয় — cv.wait(lk, [&]{ return ready; }); — কেন?

  • predicate দিলে wait দ্রুত হয়
  • Spurious wakeup — কারণ ছাড়াই জাগলে শর্ত আবার যাচাই করতে
  • predicate ছাড়া compile হয় না
  • predicate না দিলে mutex ছাড়া হয় না

(সহজ) CMake-এ main.cpp থেকে app নামের প্রোগ্রাম বানানোর লক্ষ্য (target) কোন কমান্ড ঘোষণা করে?

  • add_executable(app main.cpp)
  • add_library(app main.cpp)
  • target_link_libraries(app main.cpp)
  • project(app main.cpp)

(মাঝারি) দুই thread-এর data race খুঁজতে GCC/Clang-এর কোন flag দরকার?

  • -fsanitize=address
  • -fsanitize=undefined
  • -Wall -Wextra
  • -fsanitize=thread

(মাঝারি) ১০ লাখ int-এর ওপর লুপ চালালে std::vector সাধারণত std::list-এর চেয়ে অনেক দ্রুত কেন — যদিও দুটিই O(n)?

  • vector-এর লুপ O(log n)
  • পাশাপাশি মেমরি — cache locality
  • compiler vector-এর লুপ মুছে দেয়
  • list-এর প্রতিটি ধাপে সূচক হিসাব করতে হয়

(মাঝারি) int a = 100000, b = 100000; long long c = a * b; — c-তে কী থাকে?

  • ১০০০০০০০০০০ (১০^১০)
  • নিশ্চিতভাবে ১৪১০০৬৫৪০৮
  • Compile error
  • Undefined behavior — int-এ গুণ overflow করে

(মাঝারি) কনটেস্টে std::ios::sync_with_stdio(false); std::cin.tie(nullptr); লেখার পরে কোনটি এড়াতে হবে?

  • একই প্রোগ্রামে cout আর printf মেশানো
  • '\n' দিয়ে নতুন লাইন লেখা
  • বড় ইনপুট পড়া
  • long long পড়া

১০ · AI দিয়ে আরও MCQ বানান — কিন্তু উত্তর compile করে যাচাই করুন

এই পাতার প্রশ্ন শেষ হলে আরও অনুশীলন চাইবেন। AI চ্যাটবট মুহূর্তে শত শত MCQ বানিয়ে দিতে পারে। কিন্তু একটি সতর্কবার্তা মনে রাখুন: C++-এর চতুর MCQ-তে AI প্রায়ই ভুল অপশনকে “সঠিক” বলে। কারণ ইন্টারনেটের পুরোনো প্রশ্নব্যাংকে একই ভুল বছরের পর বছর ছড়িয়েছে — যেমন virtual destructor ছাড়া delete-কে “শুধু base destructor চলে” বলা, বা sizeof(int)-কে ২ ধরা (পুরোনো ১৬-বিট Turbo C++-এর যুগের তথ্য)। তার ওপর C++ প্রতি তিন বছরে বদলায়; AI অনেক সময় C++98-এর নিয়ম আর C++23-এর নিয়ম মিশিয়ে ফেলে।

AI-এর প্রচলিত ভুলউদাহরণকীভাবে ধরবেন
UB-কে নির্দিষ্ট ফল বলাdangling reference → “উত্তর ৫”-fsanitize=address,undefined দিয়ে চালান
compile error চিনতে না পারাunique_ptr কপি → “দুজনই মালিক”কোডটি নিজে compile করুন — error বার্তাই উত্তর
পুরোনো মানদণ্ডের নিয়ম“auto শুধু storage class” (C++11-এর আগের অর্থ)প্রশ্নে মানদণ্ড লেখা আছে কি না দেখুন; -std=c++23 দিয়ে চালান
লাইব্রেরির তথ্যকে মানদণ্ড বলা“sizeof(std::string) সব সময় ৩২”libstdc++ আর libc++ দুটিতেই চালিয়ে দেখুন (Compiler Explorer-এ সহজ)
দুটি সঠিক অপশন রাখা“কোনটি overload করা যায় না?” — :: আর . দুটিই অপশনেপ্রতিটি অপশন আলাদা করে যাচাই করুন

নিচের প্রম্পটটি AI-কে প্রশ্ন বানাতে বলে, কিন্তু সাথে এমন তথ্যও চায়, যা দিয়ে আপনি উত্তর যাচাই করতে পারবেন। প্রতিটি কোড-প্রশ্নের জন্য একটি পূর্ণ প্রোগ্রাম চাওয়াই এখানে আসল কৌশল:

আমি C++-এর MCQ পরীক্ষার প্রস্তুতি নিচ্ছি। ধরে নাও: C++23, ৬৪-বিট Linux, GCC ও libstdc++, int ৩২ বিট, long ও pointer ৬৪ বিট। বিষয়: [যেমন: constructor, copy আর move] কঠিনতা: [সহজ / মাঝারি / কঠিন] ১০টি MCQ বানাও। প্রতিটির জন্য: ১. প্রশ্ন আর ঠিক ৪টি অপশন; শুধু একটি সঠিক। ২. সঠিক উত্তর, আর প্রতিটি ভুল অপশন কেন ভুল — এক লাইনে। ৩. প্রশ্নে কোড থাকলে একটি সম্পূর্ণ, compile করা যায় এমন C++ প্রোগ্রাম দাও, যা চালালে উত্তরটি দেখা যায়। ৪. উত্তর যদি compile error, undefined behavior বা implementation-defined হয়, সেটি স্পষ্ট বলো — কোনো নির্দিষ্ট সংখ্যাকে “সঠিক” বলবে না। ৫. কোন মানদণ্ড থেকে নিয়মটি চালু, তা লেখো (যেমন “C++17 থেকে”)। ৬. কোনো প্রশ্নকে কোনো নির্দিষ্ট পরীক্ষার আসল প্রশ্ন বলে দাবি করবে না।

যাচাইয়ের চেকলিস্ট

কেন এই উত্তর? — যে প্রশ্নগুলোতে সবচেয়ে বেশি ভুল হয়

এই ছয়টি বিষয় এত বেশি ভুল বোঝা হয় যে আলাদা ব্যাখ্যা দরকার। প্রশ্নটি নিয়ে আগে নিজে ভাবুন — তারপর “→ উত্তর” চাপুন।

প্র ০১struct আর class-এর মধ্যে আসল পার্থক্য কী? “struct-এ ফাংশন রাখা যায় না” — কথাটি কি ঠিক?

কথাটি ভুল। C++-এ struct আর class একই জিনিস — দুটিতেই member function, constructor, destructor, virtual ফাংশন, inheritance, template — সব চলে। মানদণ্ডে পার্থক্য মাত্র দুটি, আর দুটিই ডিফল্টের:

  • সদস্যের ডিফল্ট access: struct-এ public, class-এ private.
  • inheritance-এর ডিফল্ট ধরন: struct D : B মানে public inheritance, class D : B মানে private inheritance.

এর বাইরে কোনো পার্থক্য নেই। struct দিয়ে লেখা টাইপও “class type”, আর আকার, মেমরির বিন্যাস বা গতিতে কোনো পার্থক্য হয় না।

দুটি keyword কেন আছে? ইতিহাস। Bjarne Stroustrup “C with Classes” বানানোর সময় চেয়েছিলেন C-এর কোড সরাসরি C++-এ compile হোক। C-তে struct ছিল, আর তার সব সদস্য সবার জন্য খোলা — তাই struct-এর ডিফল্ট public রাখতেই হলো। নতুন keyword class-এর ডিফল্ট রাখা হলো private, কারণ ডেটা লুকিয়ে রাখা (encapsulation) ছিল নতুন ভাষার মূল ভাবনা। Stroustrup দুটিকে আলাদা দুই ধরনের জিনিস বানাতে চাননি; তিনি একটি ধারণার দুটি নাম রেখেছিলেন।

তাহলে বাস্তবে কোনটি কখন? প্রচলিত রীতি — C++ Core Guidelines-ও প্রায় একই কথা বলে: যদি টাইপের কোনো invariant থাকে (যেমন “denominator কখনো ০ নয়”, “size সব সময় capacity-র সমান বা কম”), তাহলে class লিখুন, ডেটা private রাখুন, constructor দিয়ে নিয়ম রক্ষা করুন। আর যদি সদস্যগুলো স্বাধীনভাবে যেকোনো মান নিতে পারে (যেমন একটি Point { int x; int y; }), তাহলে struct — পাঠক এক নজরে বোঝেন এটি শুধু ডেটার একটি গুচ্ছ। এটি রীতি, ভাষার নিয়ম নয়।

MCQ-তে ফাঁদ সাধারণত এমন অপশনে থাকে: “struct-এ constructor রাখা যায় না”, “struct inherit করা যায় না”, “struct stack-এ থাকে, class heap-এ” (শেষটি C# থেকে আসা ধারণা — C++-এ সম্পূর্ণ ভুল; যেকোনো object যেকোনো জায়গায় থাকতে পারে)। এগুলো সবই বাদ।

মূল উপলব্ধি: struct আর class-এর পার্থক্য শুধু ডিফল্ট access আর ডিফল্ট inheritance — struct-এ public, class-এ private. বাকি সব সমান; কোনটি লিখবেন, সেটি invariant আছে কি না তার ওপর নির্ভর করে, ক্ষমতার ওপর নয়।

প্র ০২base-এর destructor virtual না হলে delete basePtr; কেন undefined behavior — “শুধু base-এর destructor চলে” কেন সঠিক উত্তর নয়?

প্রথমে ভাবুন delete p; আসলে কী করে। দুটি কাজ: object-এর destructor ডাকা, তারপর মেমরি ফেরত দেওয়া। দুটির জন্যই জানা দরকার object-এর আসল টাইপ — কোন destructor, আর কত বাইট মেমরি।

destructor virtual হলে সমস্যা নেই। Object-এর ভেতরের লুকানো vptr দেখায় তার আসল class-এর vtable-এ, আর সেখান থেকে ~Derived খুঁজে পাওয়া যায়। সেটি চলে, তারপর নিজে থেকে ~Base, তারপর সঠিক আকারের মেমরি ফেরত যায়।

destructor virtual না হলে compiler শুধু pointer-এর টাইপ দেখে — Base*. সে ~Base সরাসরি ডেকে দেয়। মানদণ্ড এখানে স্পষ্ট বলে: static টাইপ আর dynamic টাইপ আলাদা হলে, আর base-এর destructor virtual না হলে, আচরণ undefined. কেন “শুধু ~Base চলে” লিখে দেয়নি? কারণ সমস্যা শুধু destructor নয়। Multiple inheritance-এ Base* pointer-এর ঠিকানা আর পুরো object-এর শুরুর ঠিকানা আলাদা হতে পারে — তখন ভুল ঠিকানার মেমরি ফেরত দেওয়ার চেষ্টা হয়, যা heap নষ্ট করে। মানদণ্ড এমন সব পরিস্থিতির দায় এড়াতে পুরোটিকেই UB বলেছে। বাস্তবে single inheritance-এ প্রায়ই শুধু ~Base চলতে দেখা যায়, আর Derived-এর resource (ফাইল, মেমরি) leak হয় — কিন্তু সেটি একটি সম্ভাব্য ফল, নিশ্চয়তা নয়।

একটি আকর্ষণীয় ব্যতিক্রম: std::shared_ptr<Base> p = std::make_shared<Derived>(); virtual destructor ছাড়াও সঠিকভাবে ~Derived ডাকে। কারণ shared_ptr-এর control block তৈরির মুহূর্তের আসল টাইপের deleter মনে রাখে। std::unique_ptr<Base> তা করে না — সে সাধারণ delete ডাকে।

নিয়ম কী? C++ Core Guidelines-এর পরামর্শ: base class-এর destructor হয় public এবং virtual (যদি base pointer দিয়ে delete করা হবে), নয়তো protected এবং non-virtual (তখন base pointer দিয়ে delete করাই compile হবে না)। যেকোনো class-এ একটি virtual ফাংশন থাকলে destructor-ও virtual করে দেওয়া সবচেয়ে সহজ অভ্যাস; GCC-র -Wnon-virtual-dtor এ নিয়ে সতর্ক করে।

মূল উপলব্ধি: virtual destructor ছাড়া base pointer দিয়ে derived object delete করা undefined behavior — “শুধু base destructor চলে” একটি সম্ভাব্য লক্ষণ, উত্তর নয়। polymorphic base মানেই virtual destructor.

প্র ০৩std::move যদি কিছুই না সরায়, তাহলে নাম “move” কেন? আর কখন std::move লেখা উল্টো ক্ষতি করে?

std::move(x) একটি এক লাইনের ফাংশন — মূলত static_cast<T&&>(x). এটি x-কে rvalue reference হিসেবে দেখায়। এর মানে compiler-কে বলা: “এই object-এর আর দরকার নেই, চাইলে এর ভেতরের জিনিস নিয়ে যাও।” আসল সরানো (pointer চুরি করে নেওয়া, পুরোনোটিকে খালি করা) করে move constructor বা move assignment — তখনই, যখন ফলটি কোথাও বসানো হয়। শুধু std::move(x); লিখে থামলে x-এর কিছুই হয় না।

নামটি নিয়ে বিতর্ক পুরোনো। Move semantics-এর প্রস্তাব (Howard Hinnant, Peter Dimov আর Dave Abrahams, ২০০২ সালের N1377) C++11-এ এসেছিল, আর তখন থেকেই অনেকে বলেছেন নামটি হওয়া উচিত ছিল rvalue_cast বা movable. কিন্তু “move” নামটি ব্যবহারকারীর উদ্দেশ্য বোঝায় — “আমি সরাতে চাই” — তাই সেটিই থেকে গেল। দাম হলো এই প্রশ্নটি, যা প্রায় প্রতিটি ইন্টারভিউতে আসে।

তিনটি পরিস্থিতিতে std::move হয় কাজ করে না, নয়তো ক্ষতি করে:

  • const object: std::move করা const std::string হয় const std::string&&, যা move constructor নিতে পারে না — নীরবে কপি হয়। কোনো error বা warning সাধারণত আসে না।
  • local ভ্যারিয়েবল ফেরত দেওয়া: return std::move(result); লিখলে compiler আর NRVO (object সরাসরি caller-এর জায়গায় বানানো) করতে পারে না, কারণ ফেরত মান আর সাধারণ ভ্যারিয়েবলের নাম নয়। ফলে কপি-বাঁচানোর চেষ্টায় একটি বাড়তি move ঘটে। GCC এ নিয়ে -Wpessimizing-move সতর্কবার্তা দেয়। শুধু return result; লিখুন — দরকার হলে compiler নিজেই move করে।
  • move-এর পরে আবার ব্যবহার: moved-from object “valid but unspecified” — নতুন মান বসানো বৈধ, কিন্তু আগের মান ধরে নিয়ে পড়া একটি যুক্তির বাগ (use-after-move). clang-tidy এমন জায়গা খুঁজে দেয়।

আর int, double-এর মতো সাধারণ টাইপে move আর copy একই — সরানোর মতো কোনো heap মেমরি নেই।

মূল উপলব্ধি: std::move একটি cast, কোনো কাজ নয় — সরানোর সিদ্ধান্ত নেয় move constructor. const object-এ এটি নীরবে কপি হয়, আর return-এ লিখলে NRVO নষ্ট করে।

প্র ০৪std::map-এর m[key] শুধু পড়লেও কেন নতুন উপাদান ঢুকে যায়? এটি কি নকশার ভুল?

operator[]-এর কাজের বর্ণনা: “key-এর সাথে যুক্ত মানের একটি reference দাও।” এখন ভাবুন key যদি না থাকে। Reference কোনো কিছুকে দেখাতেই হবে — “খালি reference” বলে কিছু নেই। ফাংশনটির সামনে তিনটি পথ ছিল: exception ছোড়া, UB, অথবা একটি নতুন উপাদান বানিয়ে তার reference দেওয়া। STL-এর নকশাকারীরা তৃতীয়টি বেছে নিয়েছিলেন: key না থাকলে value-initialized মান (int-এর জন্য ০, string-এর জন্য খালি string) দিয়ে নতুন উপাদান ঢোকানো।

এই সিদ্ধান্তের পেছনে একটি সাধারণ ব্যবহার ছিল — গোনা। for (auto& w : words) ++count[w]; — কোনো শব্দ প্রথমবার এলে ০ থেকে শুরু হয়ে ১ হয়। কোনো if লাগে না। এই এক লাইনের সুবিধার জন্য দামটি দিতে হয় পড়ার সময়: if (m["x"] == 0) লিখে শুধু যাচাই করতে চাইলেও map-এ "x" ঢুকে যায়। বড় লুপে এভাবে map অকারণে ফুলে ওঠে।

এর তিনটি পরিণতি MCQ-তে আসে:

  • const map-এ [] compile হয় না — কারণ এটি map বদলাতে পারে। const map থেকে পড়তে at() (key না থাকলে std::out_of_range) বা find().
  • mapped type-এর ডিফল্ট constructor লাগে — যে টাইপের ডিফল্ট constructor নেই, তার map-এ [] ব্যবহার করা যায় না।
  • শুধু আছে কি না জানতে: m.contains(k) (C++20), বা m.find(k) != m.end(), বা m.count(k).

C++17 আরও দুটি পরিষ্কার উপায় এনেছে: try_emplace (key না থাকলে তবেই বানায়) আর insert_or_assign (থাকলে বদলায়, না থাকলে ঢোকায়)। std::unordered_map-এর []-এর আচরণ হুবহু একই।

তাই এটি ভুল নয়, একটি সচেতন বিনিময় — লেখার সুবিধার বদলে পড়ার ফাঁদ। প্রশ্নকর্তারা ঠিক এই ফাঁদটিই খোঁজেন।

মূল উপলব্ধি: map-এর [] মানে “খোঁজো, না পেলে বানাও।” শুধু খুঁজতে চাইলে find, contains বা at — আর const map-এ [] কখনো নয়।

প্র ০৫std::vector<bool>-কে কেন “আসল container নয়” বলা হয়? এটি কি এখনো ব্যবহার করা উচিত?

১৯৯৮ সালের প্রথম মানদণ্ডে একটি বিশেষ নিয়ম ঢোকানো হয়েছিল: std::vector<bool> সাধারণ vector হবে না — এটি একটি specialization, যা প্রতিটি bool এক বিটে রাখে। ১০ লাখ bool-এ ১ MB-র বদলে মোটামুটি ১২৫ KB — মেমরি তখন দামি ছিল, প্রস্তাবটি যুক্তিসঙ্গত মনে হয়েছিল।

সমস্যা হলো, C++-এ সবচেয়ে ছোট ঠিকানাযোগ্য একক একটি বাইট। একটি বিটের ঠিকানা হয় না, তাই তার bool&-ও হয় না। তাই vb[0] কোনো bool& দেয় না — দেয় একটি proxy object (std::vector<bool>::reference), যা “কোন শব্দের কোন বিট” মনে রাখে আর bool-এর মতো আচরণের ভান করে। এর ফল:

  • bool& r = vb[0]; compile হয় না — proxy একটি temporary.
  • auto x = vb[0]; লিখলে x একটি bool নয়, proxy — vb ধ্বংস হলে x dangling.
  • &vb[0] দিয়ে bool* পাওয়া যায় না, আর data() ফাংশন নেই — C API-তে পাঠানো যায় না।
  • সাধারণ vector-এ দুই thread আলাদা উপাদানে একসাথে লিখলে data race হয় না; কিন্তু মানদণ্ড vector<bool>-কে এই নিশ্চয়তা থেকে স্পষ্টভাবে বাদ রেখেছে — পাশাপাশি দুটি বিট একই বাইটে থাকে।

মানদণ্ডের container-এর শর্ত বলে operator[] একটি আসল reference দেবে — vector<bool> তা মানে না। এজন্য Herb Sutter-সহ অনেক বিশেষজ্ঞ ১৯৯০-এর দশকের শেষ থেকেই লিখে আসছেন যে এটি আসলে container নয়, আর specialization-টি ভুল ছিল। কিন্তু সরিয়ে ফেললে পুরোনো কোড ভাঙবে, তাই এটি আজও আছে।

কখন কী ব্যবহার করবেন? মেমরি সত্যিই বাঁচাতে হলে (যেমন কনটেস্টে বড় sieve) vector<bool> কাজের, শুধু ফাঁদগুলো মনে রাখুন। আকার compile-এর সময় জানা থাকলে std::bitset<N> আরও পরিষ্কার। আর আসল bool-এর container চাইলে std::vector<char> বা std::vector<std::uint8_t>.

মূল উপলব্ধি: vector<bool> বিট দিয়ে মেমরি বাঁচায়, কিন্তু তার [] একটি proxy দেয়, bool& নয় — তাই reference, pointer, data() আর thread-নিরাপত্তার সাধারণ নিয়ম এখানে খাটে না।

প্র ০৬const int*, int const*, int* const — কোনটিতে কী const, এক নজরে চেনার উপায় কী?

একটি সূত্র মনে রাখুন: const তার বাঁ দিকের জিনিসকে const করে; বাঁয়ে কিছু না থাকলে ডান দিকেরটিকে। আর পুরো ঘোষণাটি ডান থেকে বাঁয়ে পড়ুন — *-কে পড়ুন “pointer to”:

  • const int* p — “p is a pointer to int, যা const.” মানটি বদলানো যাবে না (*p = 5 error), কিন্তু p অন্য কোথাও দেখাতে পারে। এটি pointer to const.
  • int const* p — হুবহু একই। const বসেছে int-এর ডানে, তাই int-কেই const করছে।
  • int* const p — “p is a const pointer to int.” p নিজে বদলানো যাবে না (p = &y error), কিন্তু *p = 5 চলে। এটি const pointer, আর শুরুতেই মান দিতে হয়।
  • const int* const p — দুটিই const.

মূল ভাবনা: *-এর বাঁয়ে const মানে “যাকে দেখায়” const; *-এর ডানে const মানে pointer নিজে const.

এই কারণেই C++ জগতে “east const” আর “west const”-এর বিতর্ক আছে। West const (const int) বেশি প্রচলিত, পড়তে স্বাভাবিক। East const (int const) সমর্থকেরা বলেন, তাতে নিয়ম সব জায়গায় এক — const সব সময় তার বাঁয়েরটিকে বদলায়। Compiler-এর কাছে দুটি অভিন্ন।

একটি কঠিন ফাঁদ, যা MCQ-তে মাঝে মাঝে আসে: using IntPtr = int*; const IntPtr p = &x; — এটি কী? অনেকে লেখাটিকে মনে মনে বসিয়ে পড়েন const int* p. ভুল। type alias একটি সম্পূর্ণ টাইপ — const পুরো “int*”-কে const করে। তাই এটি int* const p: pointer নিজে const, মানটি নয়। এজন্যই pointer টাইপ লুকানো alias সাবধানে ব্যবহার করতে হয়।

Reference-এ এই প্রশ্ন ওঠে না: reference এমনিতেই অন্য কিছুতে বাঁধা যায় না, তাই int& const লেখা অর্থহীন — compiler error দেয়। const int& মানে শুধু “মানটি এই নাম দিয়ে বদলানো যাবে না।”

মূল উপলব্ধি: ডান থেকে বাঁয়ে পড়ুন। * এর বাঁয়ে const → মান const; * এর ডানে const → pointer const. আর alias-এর সামনের const পুরো pointer-টিকে const করে।

শেষ ধাপ — ক্রম সাজান আর স্কোর দেখুন

উপরের সব MCQ শেষ করলে নিচে আপনার মোট স্কোর দেখাবে — প্রথম চেষ্টায় কয়টি ঠিক হয়েছে।

ক্রমclass D : public B-এর দুটি সদস্য object আছে। D d; একটি block-এ তৈরি হয়ে block শেষে ধ্বংস হলে ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. 🧱 base B-এর constructor
  2. 🔩 সদস্যদের constructor — class-এ ঘোষণার ক্রমে
  3. 🏗️ D-এর constructor-এর body
  4. 🧹 D-এর destructor-এর body
  5. 🔧 সদস্যদের destructor — ঘোষণার উল্টো ক্রমে
  6. 🪨 base B-এর destructor

শব্দকোষ

লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব শব্দ এখানে একসাথে:

মনে রাখুন

  1. আগে দুটি প্রশ্ন: কোড থাকলে আউটপুট খোঁজার আগে জিজ্ঞেস করুন — “এটি কি compile হবে?” আর “compile হলে কি UB?” অনেক কঠিন প্রশ্নের উত্তর এখানেই মেলে।
  2. বাদ দিন, খুঁজবেন না: প্রতিটি অপশনের পেছনের নিয়ম যাচাই করুন; ভুল অপশন দেখেই বোঝা যায় প্রশ্নটি কোন ভুল ধারণা ধরতে চায় — যেমন v(3, 5) বনাম v{3, 5}.
  3. OOP-এর তিন ফাঁদ: polymorphic base-এ virtual destructor; সদস্য তৈরি হয় ঘোষণার ক্রমে; value-তে কপি করলে slicing — virtual ডাকও তখন base-এর।
  4. STL-এর তিন ফাঁদ: map-এর [] না পেলে ঢোকায়; std::remove size বদলায় না; reallocation-এর পরে পুরোনো iterator অচল।
  5. প্ল্যাটফর্ম ও মানদণ্ড ছাড়া উত্তর নেই: sizeof(char) = ১ ছাড়া সব আকার প্ল্যাটফর্ম বা লাইব্রেরির; ভুলে নম্বর কাটলে হিসাব করে আন্দাজ করুন; AI-এর বানানো প্রশ্ন compile করে যাচাই করুন।

পরের পর্বে: MCQ-তে চারটি অপশন থেকে বাছতে হয়। লিখিত আর মৌখিক (ভাইভা) পরীক্ষায় কোনো অপশন নেই — নিজের ভাষায় সংজ্ঞা, পার্থক্য আর উদাহরণ দিতে হয়। পর্ব ৫৪-এ সেই প্রশ্নগুলো আর উত্তর সাজানোর কৌশল।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

এই পর্বটি শেয়ার করুন:
এই পর্বে ভুল পেয়েছেন? জানান