Curated questions covering OOP, STL, templates, memory management, virtual functions, smart pointers, and modern C++11/14/17/20 features.
C++ is a general-purpose language extending C with OOP, templates, exceptions, and the STL. Key additions over C: classes and objects, inheritance, polymorphism, encapsulation, function/operator overloading, references, templates, exceptions, and the Standard Template Library.
struct Point { int x, y; }; // public by default
class Circle { int radius; }; // private by default
class Animal {};
class Dog : public Animal {}; // is-a
class Engine {};
class Car : private Engine {}; // implemented-in-terms-of
A virtual function is declared with the virtual keyword in the base class. When called through a base class pointer/reference, the derived class version is called at runtime (dynamic dispatch via vtable).
class Shape {
public:
virtual double area() const = 0; // pure virtual
virtual ~Shape() {} // virtual destructor
};
class Circle : public Shape {
double r;
public:
Circle(double r) : r(r) {}
double area() const override { return 3.14 * r * r; }
};
Shape* s = new Circle(5);
printf("%.2f", s->area()); // calls Circle::area()
class Base {
public:
virtual void foo() { cout << "Base"; } // virtual
virtual void bar() = 0; // pure virtual
};
// Overloading
void print(int x) {}
void print(double x) {}
// Overriding
class Base { virtual void show() { cout << "Base"; } };
class Derived : public Base { void show() override { cout << "Derived"; } };
class MyClass {
public:
MyClass() {} // default
MyClass(int x) : val(x) {} // parameterized
MyClass(const MyClass& o) : val(o.val) {} // copy
MyClass(MyClass&& o) : val(o.val) { o.val = 0; } // move
private:
int val;
};
MyClass a(5);
MyClass b = a; // copy constructor
MyClass c;
c = a; // assignment operator
A destructor (~ClassName) is called automatically when an object goes out of scope or is deleted. Used to release resources. Always declare destructors virtual in base classes to ensure proper cleanup of derived objects.
class Resource {
int* data;
public:
Resource() { data = new int[100]; }
~Resource() { delete[] data; } // cleanup
};
class Base {
public:
virtual ~Base() {} // virtual destructor - essential!
int* p = new int(42); // calls constructor
delete p; // calls destructor
int* arr = new int[10];
delete[] arr; // use delete[] for arrays
Smart pointers automatically manage memory, preventing leaks. Defined in
auto up = make_unique<int>(42); // unique ownership
auto sp = make_shared<int>(42); // shared ownership
weak_ptr<int> wp = sp; // non-owning
// No manual delete needed!
auto u = make_unique<MyClass>();
// auto u2 = u; // ERROR: cannot copy
auto u2 = move(u); // OK: transfer ownership
auto s1 = make_shared<MyClass>();
auto s2 = s1; // OK: both own the object
RAII is a C++ idiom where resource acquisition (memory, file handles, locks) is tied to object lifetime. Resources are acquired in the constructor and released in the destructor. Smart pointers, std::fstream, and std::lock_guard all use RAII.
class FileHandle {
FILE* fp;
public:
FileHandle(const char* name) { fp = fopen(name, "r"); }
~FileHandle() { if (fp) fclose(fp); } // auto-cleanup
};
// File is automatically closed when FileHandle goes out of scope
The Standard Template Library provides generic containers, algorithms, and iterators.
vector<int> v = {3,1,4,1,5};
sort(v.begin(), v.end());
auto it = find(v.begin(), v.end(), 4);
accumulate(v.begin(), v.end(), 0); // sum
vector<int> v = {1,2,3};
v.push_back(4); // O(1) amortized
v[2]; // O(1) random access
list<int> l = {1,2,3};
auto it = l.begin();
advance(it, 1);
l.insert(it, 99); // O(1) insertion
map<string,int> m; // sorted, O(log n)
unordered_map<string,int> um; // hash, O(1) avg
m["key"] = 1;
um["key"] = 1;
Templates enable generic programming - writing code that works with any type. Function templates and class templates are instantiated by the compiler for each type used.
// Function template
template<typename T>
T max(T a, T b) { return a > b ? a : b; }
max(3, 5); // T = int
max(3.14, 2.71); // T = double
// Class template
template<typename T>
class Stack {
vector<T> data;
public:
void push(T val) { data.push_back(val); }
T pop() { T v = data.back(); data.pop_back(); return v; }
};
Template specialization provides a custom implementation for a specific type, overriding the generic template.
template<typename T>
void print(T val) { cout << val; } // generic
// Specialization for bool
template<>
void print<bool>(bool val) {
cout << (val ? "true" : "false");
}
int x = 5;
int& ref = x; // reference - must be initialized
ref = 10; // modifies x directly
int* ptr = &x; // pointer
*ptr = 10; // must dereference
void byValue(string s) {} // copies string
void byConstRef(const string& s) {} // no copy, read-only
void byRef(string& s) {} // no copy, can modify
Move semantics allow transferring resources from a temporary (rvalue) object instead of copying them. This avoids expensive deep copies. Use std::move() to cast to rvalue reference.
vector<int> v1 = {1,2,3,4,5};
vector<int> v2 = move(v1); // v1 is now empty, no copy
// Move constructor
MyClass(MyClass&& other) noexcept
: data(other.data) {
other.data = nullptr; // transfer ownership
}
int x = 5; // x is lvalue, 5 is rvalue
int& r = x; // lvalue reference
int&& rr = 5; // rvalue reference
int&& rr2 = move(x); // cast lvalue to rvalue
Lambdas are anonymous function objects. Syntax: [capture](params) -> return_type { body }.
auto add = [](int a, int b) { return a + b; };
cout << add(3, 4); // 7
// Capture by value and reference
int x = 10;
auto f1 = [x]() { return x; }; // capture by value
auto f2 = [&x]() { x++; }; // capture by reference
auto f3 = [=]() { return x; }; // capture all by value
auto f4 = [&]() { x++; }; // capture all by reference
// With STL
vector<int> v = {3,1,4,1,5};
sort(v.begin(), v.end(), [](int a, int b) { return a > b; });
vector<int> v = {3,1,4,1,5,9};
sort(v.begin(), v.end()); // ascending
sort(v.begin(), v.end(), greater<int>()); // descending
stable_sort(v.begin(), v.end()); // preserves equal order
stack<int> s;
s.push(1); s.push(2);
cout << s.top(); // 2 (LIFO)
queue<int> q;
q.push(1); q.push(2);
cout << q.front(); // 1 (FIFO)
set<int> s = {3,1,4,1,5}; // {1,3,4,5} - no duplicates
multiset<int> ms = {3,1,4,1,5}; // {1,1,3,4,5} - duplicates OK
vector<pair<int,string>> v;
v.push_back({1, "hello"}); // creates temporary, then moves
v.emplace_back(1, "hello"); // constructs in-place, no temporary
auto x = 42; // int
auto v = vector<int>(); // vector<int>
int a = 5;
decltype(a) b = 10; // int (same type as a)
decltype(a + 1.0) c; // double
void f(int x) { cout << "int"; }
void f(int* p) { cout << "ptr"; }
f(NULL); // ambiguous - might call f(int)
f(nullptr); // unambiguous - calls f(int*)
class Base { virtual void foo() {} };
class Derived : public Base {
void foo() override {} // compile error if Base::foo not virtual
};
class Leaf : public Derived {
void foo() final {} // cannot be overridden further
};
Base* b = new Derived();
Derived* d = dynamic_cast<Derived*>(b); // safe runtime cast
if (d) { /* cast succeeded */ }
string s = "Hello";
s += " World"; // easy concatenation
s.length(); // 11
s.substr(0, 5); // "Hello"
s.find("World"); // 6
array<int, 5> arr = {1,2,3,4,5}; // fixed size
vector<int> v = {1,2,3}; // dynamic
v.push_back(4); // can grow
std::optional
optional<string> findUser(int id) {
if (id == 1) return "Alice";
return nullopt; // no value
}
auto user = findUser(1);
if (user.has_value()) {
cout << user.value();
}
cout << user.value_or("Unknown");
variant<int, string, double> v = 42;
v = "hello";
cout << get<string>(v); // "hello"
// Type-safe visitor
visit([](auto& val) { cout << val; }, v);
any a = 42;
a = string("hello");
cout << any_cast<string>(a); // "hello"
// any_cast<int>(a); // throws bad_any_cast
const int x = 5; // may be runtime
constexpr int y = 5; // must be compile-time
constexpr int factorial(int n) {
return n <= 1 ? 1 : n * factorial(n-1);
}
constexpr int f5 = factorial(5); // computed at compile time
// Thread
thread t([]{ doWork(); });
t.join();
// Async
auto future = async(launch::async, []{ return compute(); });
int result = future.get(); // blocks until done
mutex mtx;
// Manual (risky)
mtx.lock();
// critical section
mtx.unlock();
// RAII (preferred)
{
lock_guard<mutex> lock(mtx);
// critical section - auto-unlocked
}
std::string_view (C++17) is a non-owning, read-only view of a string. It does not copy the string data. Use it for function parameters that only need to read a string.
void print(string_view sv) { // no copy
cout << sv;
}
string s = "Hello";
print(s); // works
print("World"); // works - no allocation
print(s.substr(0,3)); // works - no copy
// std::tie
int a, b;
tie(a, b) = make_pair(1, 2);
// Structured bindings (C++17)
auto [x, y] = make_pair(1, 2);
map<string,int> m;
for (auto& [key, val] : m) {
cout << key << ": " << val;
}
std::span
void process(span<int> data) {
for (int x : data) cout << x;
}
int arr[] = {1,2,3,4,5};
process(arr); // works
vector<int> v = {1,2,3};
process(v); // works - no copy
std::format (C++20) is a type-safe, extensible string formatting function. Unlike printf, it is type-safe (no format string mismatches), supports custom types, and returns a string.
// printf - not type-safe
printf("Hello %s, you are %d years old\n", name, age);
// std::format (C++20) - type-safe
string s = format("Hello {}, you are {} years old", name, age);
cout << format("{:.2f}", 3.14159); // "3.14"
std::ranges (C++20) provides range-based versions of STL algorithms that work directly on containers without needing begin()/end() pairs. They also support views (lazy transformations) and pipelines.
vector<int> v = {3,1,4,1,5,9};
// Traditional
sort(v.begin(), v.end());
// Ranges (C++20)
ranges::sort(v);
// Views pipeline
auto result = v | views::filter([](int x){ return x > 3; })
| views::transform([](int x){ return x * 2; });
Coroutines (C++20) are functions that can be suspended and resumed. They use co_await, co_yield, and co_return. Used for async programming, generators, and cooperative multitasking without threads.
// Generator coroutine
generator<int> fibonacci() {
int a = 0, b = 1;
while (true) {
co_yield a;
tie(a, b) = make_pair(b, a + b);
}
}
class Buffer {
public:
Buffer(Buffer&& other) noexcept; // move constructor
Buffer& operator=(Buffer&&) noexcept; // move assignment
};
Buffer makeBuffer() {
return Buffer{}; // guaranteed copy elision in C++17
}
class FileHandle {
unique_ptr<FILE, decltype(&fclose)> file;
public:
FileHandle(FILE* f) : file(f, fclose) {}
};
// Rule of Zero: unique_ptr owns cleanup safely.
noexcept tells the compiler and callers that a function should not throw. It helps optimization and affects move operations in standard containers. Old dynamic throw specifications like throw(Type) are deprecated/removed and should not be used.
class Item {
public:
Item(Item&& other) noexcept; // vector can move safely during reallocation
};
void save() noexcept {
// must not let exceptions escape
}
Explore 500+ free tutorials across 20+ languages and frameworks.