C++ interviews test memory management, object-oriented design, template metaprogramming, the STL, concurrency, and modern C++11–20 features. This guide covers the 50 most common questions — with clear answers and runnable examples.
Quick reference
| Topic | Most asked questions |
|---|---|
| Memory | Stack vs heap, new/delete, smart pointers, RAII |
| OOP | Virtual functions, vtable, abstract classes, multiple inheritance |
| Templates | Function templates, class templates, specialization, SFINAE |
| STL | vector vs list, map vs unordered_map, iterators, algorithms |
| Move semantics | lvalue vs rvalue, move constructor, perfect forwarding |
| Concurrency | threads, mutex, atomic, condition_variable |
| Modern C++ | auto, lambda, constexpr, structured bindings, ranges (C++20) |
Memory management
1. What is the difference between stack and heap memory?
Stack memory is automatically managed — allocated when a function is called and released when it returns. It is fast but limited in size. Heap memory is manually managed (or via smart pointers) and lasts until explicitly freed.
void example() {
int x = 42; // stack — freed when function returns
int* p = new int(42); // heap — must call delete p;
delete p;
}
Stack overflow occurs when recursion or large local arrays exceed the stack limit. Heap fragmentation and leaks occur when new is not paired with delete.
2. What are smart pointers? When do you use each?
Smart pointers manage heap memory automatically via RAII. The three main types:
| Smart pointer | Ownership | Use case |
|---|---|---|
unique_ptr<T> |
Exclusive | Single owner; move-only |
shared_ptr<T> |
Shared (ref-counted) | Multiple owners |
weak_ptr<T> |
Non-owning observer | Break shared_ptr cycles |
#include <memory>
// unique_ptr — sole owner
auto u = std::make_unique<int>(42);
// shared_ptr — shared ownership
auto s1 = std::make_shared<int>(42);
auto s2 = s1; // ref count → 2
// weak_ptr — observe without ownership
std::weak_ptr<int> w = s1;
if (auto locked = w.lock()) {
std::cout << *locked << "\n";
}
Rule: prefer unique_ptr by default; use shared_ptr only when shared ownership is truly needed; use weak_ptr to break cycles (e.g., parent–child back-pointers).
3. What is RAII?
RAII (Resource Acquisition Is Initialization) is the C++ idiom of tying resource lifetime to object lifetime. The resource is acquired in the constructor and released in the destructor, so it is automatically freed when the object goes out of scope — even if an exception is thrown.
class FileHandle {
FILE* f_;
public:
explicit FileHandle(const char* path) : f_(fopen(path, "r")) {
if (!f_) throw std::runtime_error("cannot open file");
}
~FileHandle() { fclose(f_); } // always called
FILE* get() const { return f_; }
};
Smart pointers, std::lock_guard, std::fstream, and std::vector all follow RAII.
4. What is the difference between new/delete and malloc/free?
| Feature | new/delete |
malloc/free |
|---|---|---|
| Calls constructors/destructors | Yes | No |
| Returns typed pointer | Yes | void* |
| On failure | throws std::bad_alloc |
returns nullptr |
| Array form | new[]/delete[] |
malloc(n*sizeof(T)) |
| C++ idiomatic | Yes | No (C style) |
Mixing new with free or malloc with delete is undefined behavior.
5. What is a memory leak? How do you detect one?
A memory leak occurs when heap memory is allocated but never freed, causing the process to consume ever-increasing memory. Common causes: forgetting delete, early returns before delete, exceptions thrown before cleanup.
Detection tools:
- Valgrind (
valgrind --leak-check=full ./app) - AddressSanitizer (
-fsanitize=address) - Visual Studio Diagnostics Tools (Windows)
- Using smart pointers prevents most leaks by design.
Object-oriented programming
6. What is the difference between struct and class in C++?
The only difference is the default access specifier:
struct |
class |
|
|---|---|---|
| Default member access | public |
private |
| Default inheritance | public |
private |
| Conventional use | Plain data (POD) | Full OOP types |
struct Point { int x, y; }; // members public by default
class Circle { int radius_; }; // radius_ private by default
7. What is a virtual function? How does the vtable work?
A virtual function enables runtime polymorphism — the correct overriding function is called based on the actual (dynamic) type of the object, not the static type.
class Animal {
public:
virtual void speak() const { std::cout << "...\n"; }
virtual ~Animal() = default; // always virtual destructor in base
};
class Dog : public Animal {
public:
void speak() const override { std::cout << "Woof\n"; }
};
Animal* a = new Dog();
a->speak(); // prints "Woof" — dynamic dispatch
delete a;
vtable: Each class with virtual functions gets a static table of function pointers (vtable). Each object holds a hidden vptr pointing to the class's vtable. A virtual call dereferences vptr to find the correct function — one indirection level compared to a non-virtual call.
8. What is a pure virtual function and an abstract class?
A pure virtual function (= 0) has no default implementation in the base class. A class with at least one pure virtual function is abstract and cannot be instantiated.
class Shape {
public:
virtual double area() const = 0; // pure virtual
virtual ~Shape() = default;
};
class Circle : public Shape {
double r_;
public:
explicit Circle(double r) : r_(r) {}
double area() const override { return 3.14159 * r_ * r_; }
};
// Shape s; // error: cannot instantiate abstract class
Shape* s = new Circle(5.0);
9. What is multiple inheritance and what is the diamond problem?
C++ allows a class to inherit from multiple base classes. The diamond problem occurs when two base classes share a common ancestor, and the derived class gets two copies of the grandparent.
struct A { int x; };
struct B : A {};
struct C : A {};
struct D : B, C {}; // D has two copies of A::x — ambiguous
// Fix: virtual inheritance
struct B2 : virtual A {};
struct C2 : virtual A {};
struct D2 : B2, C2 {}; // only one shared copy of A::x
10. What is the Rule of Three / Five / Zero?
| Rule | When to apply | What to define |
|---|---|---|
| Rule of Three (C++03) | Class manages a resource | Copy constructor, copy assignment, destructor |
| Rule of Five (C++11) | Class manages a resource | Above + move constructor, move assignment |
| Rule of Zero | No raw resource management | Use RAII wrappers; define nothing |
// Rule of Five example (manages raw pointer)
class Buffer {
char* data_;
size_t size_;
public:
Buffer(size_t n) : data_(new char[n]), size_(n) {}
~Buffer() { delete[] data_; }
// Copy
Buffer(const Buffer& o) : data_(new char[o.size_]), size_(o.size_) {
std::copy(o.data_, o.data_ + size_, data_);
}
Buffer& operator=(const Buffer& o) {
Buffer tmp(o); std::swap(data_, tmp.data_); std::swap(size_, tmp.size_);
return *this;
}
// Move
Buffer(Buffer&& o) noexcept : data_(o.data_), size_(o.size_) {
o.data_ = nullptr; o.size_ = 0;
}
Buffer& operator=(Buffer&& o) noexcept {
std::swap(data_, o.data_); std::swap(size_, o.size_); return *this;
}
};
Move semantics & value categories
11. What is the difference between lvalue and rvalue?
An lvalue has a persistent identity (an address you can take). An rvalue is a temporary value that does not persist beyond the expression.
int x = 5;
int* p = &x; // x is lvalue — has address
int* q = &5; // error: 5 is rvalue — no address
std::string s = std::string("hello"); // "hello" is rvalue
C++11 added rvalue references (&&) to distinguish temporaries and enable move semantics.
12. What is move semantics? Why is it important?
Move semantics allow "stealing" resources from an rvalue (temporary) instead of copying them, which is O(1) instead of O(n) for containers.
std::vector<int> make_vec() {
std::vector<int> v = {1, 2, 3, 4, 5};
return v; // RVO or move — no deep copy
}
std::vector<int> a = {1, 2, 3};
std::vector<int> b = std::move(a); // a is now empty; b owns the data
std::move casts to rvalue reference — it does not itself move anything; it just enables the move constructor/assignment to be selected.
13. What is perfect forwarding?
Perfect forwarding passes arguments to another function while preserving their value category (lvalue stays lvalue, rvalue stays rvalue). It uses forwarding references (T&& in a deduced context) and std::forward.
template<typename T>
void wrapper(T&& arg) {
target(std::forward<T>(arg)); // forwards lvalue as lvalue, rvalue as rvalue
}
This is the core technique in factory functions and std::make_unique/std::make_shared.
Templates
14. What are function templates and class templates?
Templates generate type-specific code at compile time — they are C++'s primary mechanism for generic programming.
// Function template
template<typename T>
T max_val(T a, T b) { return a > b ? a : b; }
auto m1 = max_val(3, 5); // T deduced as int
auto m2 = max_val(3.0, 5.0); // T deduced as double
// Class template
template<typename T, size_t N>
class Array {
T data_[N];
public:
T& operator[](size_t i) { return data_[i]; }
size_t size() const { return N; }
};
Array<int, 4> arr;
15. What is template specialization?
Template specialization provides a custom implementation for a specific type.
// Primary template
template<typename T>
struct IsPointer { static constexpr bool value = false; };
// Full specialization for pointer types
template<typename T>
struct IsPointer<T*> { static constexpr bool value = true; };
static_assert(!IsPointer<int>::value);
static_assert(IsPointer<int*>::value);
Partial specialization (for class templates only) matches a pattern, e.g., all pointer types T*.
16. What is SFINAE?
SFINAE (Substitution Failure Is Not An Error): when template argument substitution fails, the compiler silently removes that overload from the candidate set instead of issuing an error.
#include <type_traits>
// Only enabled when T is integral
template<typename T>
std::enable_if_t<std::is_integral_v<T>, T>
double_it(T x) { return x * 2; }
// Only enabled when T is floating-point
template<typename T>
std::enable_if_t<std::is_floating_point_v<T>, T>
double_it(T x) { return x * 2.0; }
double_it(3); // calls integral version
double_it(3.14); // calls floating-point version
C++20 concepts replace SFINAE with clearer syntax.
17. What are variadic templates?
Variadic templates accept zero or more template arguments using the ... pack syntax.
// Print any number of arguments
template<typename T>
void print(T&& t) { std::cout << t << "\n"; }
template<typename T, typename... Args>
void print(T&& first, Args&&... rest) {
std::cout << first << " ";
print(std::forward<Args>(rest)...); // recurse with remaining args
}
print(1, "hello", 3.14); // 1 hello 3.14
C++17 fold expressions simplify many variadic patterns:
template<typename... Args>
auto sum(Args... args) { return (args + ...); } // fold expression
STL containers and algorithms
18. When do you use vector vs list vs deque?
| Container | Access | Insert/remove front | Insert/remove back | Insert/remove middle | Memory |
|---|---|---|---|---|---|
vector |
O(1) random | O(n) | O(1) amortized | O(n) | Contiguous |
list |
O(n) | O(1) | O(1) | O(1) with iterator | Non-contiguous |
deque |
O(1) random | O(1) | O(1) | O(n) | Chunked |
Default: use vector. Use list only when you need stable iterators after insert/erase in the middle. deque for double-ended queues.
19. What is the difference between map and unordered_map?
| Feature | map |
unordered_map |
|---|---|---|
| Underlying structure | Red-Black tree (BST) | Hash table |
| Lookup | O(log n) | O(1) average, O(n) worst |
| Key order | Sorted | Unordered |
| Requires | operator< |
std::hash<K> + operator== |
| Iterator stability | Stable after insert | Rehash invalidates all |
| Memory | Pointer-per-node overhead | Load-factor dependent |
Use unordered_map for performance-critical lookups; map when ordered iteration matters.
20. What are iterators and iterator categories?
Iterators are objects that point to elements in a range and support traversal. Categories from weakest to strongest:
| Category | Operations | Examples |
|---|---|---|
| Input | ++, * (read once) |
istream_iterator |
| Output | ++, * (write once) |
ostream_iterator |
| Forward | ++, * (multi-pass) |
forward_list |
| Bidirectional | ++, -- |
list, set |
| Random access | ++, --, +n, -n, [] |
vector, deque |
| Contiguous (C++17) | Random + elements in memory | vector, array |
Algorithms require minimum iterator categories; passing weaker ones fails to compile.
21. How does std::sort work and what does it require?
std::sort requires random access iterators and a strict weak ordering (i.e., < or a custom comparator that is irreflexive, asymmetric, and transitive). The standard mandates O(n log n) worst case; most implementations use introsort (quicksort + heapsort fallback).
std::vector<int> v = {5, 2, 8, 1};
std::sort(v.begin(), v.end()); // ascending
std::sort(v.begin(), v.end(), std::greater<>{}); // descending
// Custom comparator
std::sort(v.begin(), v.end(), [](int a, int b){ return a % 3 < b % 3; });
std::stable_sort preserves relative order of equal elements (O(n log² n) without extra memory).
Pointers, references, and const
22. What is the difference between a pointer and a reference?
| Feature | Pointer | Reference |
|---|---|---|
| Null value | Can be nullptr |
Cannot be null |
| Rebinding | Can point to another object | Cannot be rebound |
| Indirection | *ptr to dereference |
Implicit dereference |
| Arithmetic | Supports ptr+n |
Not supported |
| Use in optional | Yes | No |
Use references for "always valid" aliasing; use pointers when null or rebinding is needed.
23. Explain const with pointers.
int x = 1, y = 2;
const int* p1 = &x; // pointer to const int — cannot modify *p1
int* const p2 = &x; // const pointer — cannot rebind p2, but can modify *p2
const int* const p3 = &x; // both const
p1 = &y; // ok — rebind pointer
// *p1 = 5; // error — *p1 is const
*p2 = 5; // ok — can modify value
// p2 = &y; // error — p2 is const
Tip: read right-to-left: const int* → "pointer to const int"; int* const → "const pointer to int".
24. What is constexpr and how does it differ from const?
const means the value cannot be changed after initialization, but it may be computed at runtime. constexpr guarantees computation at compile time.
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 (120)
int arr[factorial(4)]; // ok — compile-time constant
C++20 expanded constexpr to allow more constructs (dynamic allocation, virtual functions).
Exception handling
25. How does C++ exception handling work?
try {
if (condition) throw std::runtime_error("oops");
// ...
} catch (const std::runtime_error& e) {
std::cerr << e.what() << "\n";
} catch (const std::exception& e) {
// catches all standard exceptions
} catch (...) {
// catches anything
}
Exceptions propagate up the call stack until caught. Stack unwinding calls destructors of all local objects in the frames between throw and catch — this is why RAII is critical.
26. What is noexcept?
noexcept declares that a function will not throw. The compiler can generate more efficient code (especially for move operations), and callers can reason about exception safety.
void safe_function() noexcept { /* guaranteed not to throw */ }
// noexcept conditional
template<typename T>
void swap(T& a, T& b) noexcept(std::is_nothrow_move_constructible_v<T> &&
std::is_nothrow_move_assignable_v<T>) {
T tmp = std::move(a);
a = std::move(b);
b = std::move(tmp);
}
If a noexcept function does throw, std::terminate is called. Move constructors should be noexcept to allow containers to use them (otherwise vector falls back to copy on reallocation).
Concurrency
27. How do you create and join threads in C++?
#include <thread>
void task(int id) { std::cout << "Thread " << id << "\n"; }
int main() {
std::thread t1(task, 1);
std::thread t2(task, 2);
t1.join(); // wait for t1 to finish
t2.join();
}
detach() runs the thread independently; you must ensure the thread does not access destroyed objects after main returns.
28. What is a race condition and how do you prevent it?
A race condition occurs when two threads access shared data concurrently and at least one access is a write.
#include <mutex>
int counter = 0;
std::mutex mtx;
void increment() {
std::lock_guard<std::mutex> lock(mtx); // RAII lock
++counter;
}
Tools: std::mutex, std::atomic<T> (for simple types), std::shared_mutex (multiple readers / single writer).
29. What is std::atomic?
std::atomic<T> provides lock-free, thread-safe operations on a single object without explicit mutexes, for types like int, bool, and pointers.
#include <atomic>
std::atomic<int> count{0};
// Thread-safe without mutex
count.fetch_add(1, std::memory_order_relaxed);
int val = count.load(std::memory_order_acquire);
Memory orders (relaxed, acquire, release, seq_cst) control how operations are synchronized across CPUs.
30. What is std::condition_variable?
A condition variable lets threads wait for a condition to become true without busy-waiting.
#include <condition_variable>
#include <queue>
std::queue<int> q;
std::mutex mtx;
std::condition_variable cv;
// Producer
void produce(int val) {
std::lock_guard<std::mutex> lock(mtx);
q.push(val);
cv.notify_one();
}
// Consumer
int consume() {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []{ return !q.empty(); }); // spurious-wake safe
int val = q.front(); q.pop();
return val;
}
Modern C++ (C++11 to C++20)
31. What is auto and when should you use it?
auto deduces the type from the initializer at compile time. Use it to:
- Avoid long iterator types (
auto it = v.begin()) - Capture lambda types (which have no writable name)
- Avoid accidentally copying a view or proxy object
auto x = 42; // int
auto d = 3.14; // double
auto it = vec.begin(); // std::vector<int>::iterator
auto f = [](int a){ return a*2; }; // lambda type
Avoid auto when the type is not obvious from the right-hand side; explicit types aid readability.
32. What is a lambda expression?
A lambda is an anonymous function object defined inline.
// [capture](params) -> return_type { body }
auto add = [](int a, int b) { return a + b; };
std::cout << add(3, 4) << "\n"; // 7
int offset = 10;
auto add_offset = [offset](int x) { return x + offset; }; // capture by value
auto mutate = [&offset](int x) { offset += x; }; // capture by reference
Lambdas are commonly passed to algorithms:
std::sort(v.begin(), v.end(), [](int a, int b){ return a > b; });
33. What are range-based for loops?
std::vector<int> v = {1, 2, 3};
for (int x : v) { /* copy */ }
for (int& x : v) { x *= 2; /* modify in-place */ }
for (const int& x : v) { /* read without copy */ }
// Works on any type with begin()/end()
34. What is nullptr?
nullptr is a type-safe null pointer constant introduced in C++11, replacing NULL (which is typically 0 or (void*)0).
void f(int*) { std::cout << "pointer\n"; }
void f(int) { std::cout << "int\n"; }
f(nullptr); // unambiguously calls f(int*)
f(NULL); // might be ambiguous — NULL is 0 (int)
35. What are structured bindings (C++17)?
Structured bindings decompose aggregates (pairs, tuples, structs, arrays) into named variables.
auto [key, value] = std::make_pair("age", 30);
std::cout << key << ": " << value << "\n";
std::map<std::string, int> m = {{"alice", 1}, {"bob", 2}};
for (const auto& [k, v] : m) {
std::cout << k << " = " << v << "\n";
}
36. What are concepts (C++20)?
Concepts are named requirements on template parameters, replacing complex SFINAE with readable constraints.
#include <concepts>
template<typename T>
concept Numeric = std::integral<T> || std::floating_point<T>;
template<Numeric T>
T square(T x) { return x * x; }
square(4); // ok — int is Numeric
square(2.5); // ok — double is Numeric
// square("hi"); // error: string is not Numeric
Standard concepts include std::same_as, std::derived_from, std::convertible_to, std::invocable, and std::ranges::range.
37. What are C++20 ranges?
Ranges provide composable, lazy sequence operations via the pipe | operator.
#include <ranges>
#include <vector>
std::vector<int> v = {1, 2, 3, 4, 5, 6};
auto result = v
| std::views::filter([](int x){ return x % 2 == 0; })
| std::views::transform([](int x){ return x * x; });
for (int x : result) std::cout << x << " "; // 4 16 36
Views are lazy — no intermediate containers are created.
Object lifetime and copy control
38. What is the copy-and-swap idiom?
Copy-and-swap makes copy/move assignment operators exception-safe and eliminates duplication:
class Buffer {
std::size_t size_;
char* data_;
public:
// ... constructor, destructor, copy constructor ...
friend void swap(Buffer& a, Buffer& b) noexcept {
using std::swap;
swap(a.size_, b.size_);
swap(a.data_, b.data_);
}
Buffer& operator=(Buffer other) { // takes by value → copy already made
swap(*this, other); // steal resources from copy
return *this; // other's destructor releases old data
}
};
39. What is object slicing?
Slicing occurs when a derived-class object is assigned to a base-class variable by value, losing derived members.
struct Base { int x; };
struct Derived : Base { int y; };
Derived d{1, 2};
Base b = d; // b.y is gone — sliced off
// Fix: use pointers or references
Base& ref = d; // no slicing — ref sees Derived
Miscellaneous
40. What is undefined behavior (UB)?
Undefined behavior means the C++ standard places no requirements on what the program does. Common sources:
| UB source | Example |
|---|---|
| Signed integer overflow | INT_MAX + 1 |
| Null pointer dereference | *nullptr |
| Use after free | delete p; *p = 1; |
| Out-of-bounds access | arr[10] when size is 5 |
| Data race | Two threads write to same non-atomic variable |
| Uninitialized variable read | int x; return x; |
Compilers may assume UB never occurs, leading to surprising optimizations. Use sanitizers (-fsanitize=address,undefined) in development.
41. What is the difference between static_cast, dynamic_cast, reinterpret_cast, and const_cast?
| Cast | Purpose | Safe? |
|---|---|---|
static_cast |
Compile-time checked conversions (numeric, up/downcasting with known type) | Mostly |
dynamic_cast |
Downcast through polymorphic hierarchy; returns nullptr/throws on failure |
Yes (runtime check) |
reinterpret_cast |
Bit-level reinterpretation; pointer↔integer, unrelated pointer types | Rarely |
const_cast |
Remove const/volatile qualifier |
Only safe if original was non-const |
// dynamic_cast example
Base* b = new Derived();
if (Derived* d = dynamic_cast<Derived*>(b)) {
d->derived_method();
}
42. What is inline and when should you use it?
inline is a hint to the compiler to substitute the function body at the call site (avoiding function call overhead) and tells the linker to allow multiple identical definitions across translation units.
Modern compilers inline aggressively without the keyword; inline is more useful today for:
- Header-only libraries: define a function in a header without violating the One Definition Rule (ODR).
inline constexprvariables (C++17): avoid ODR issues with constants in headers.
43. What are anonymous (unnamed) namespaces?
An unnamed namespace restricts the visibility of names to the current translation unit — similar to static at file scope, but also works for types and classes.
namespace {
// Only visible in this .cpp file
int helper(int x) { return x * 2; }
struct InternalType { int data; };
}
Prefer unnamed namespaces over static for translation-unit-local declarations in C++.
44. What is the One Definition Rule (ODR)?
The ODR states:
- Any function, variable, class, or template may be defined at most once across the entire program (linker errors if violated).
- Types (classes, enums) and templates may be defined in multiple translation units if all definitions are identical (typically via headers).
Violations produce multiple definition linker errors. Mitigations: include guards (#pragma once), inline functions in headers, and unnamed namespaces.
45. How does std::string_view differ from std::string?
std::string_view (C++17) is a non-owning, read-only reference to a contiguous sequence of characters.
| Feature | std::string |
std::string_view |
|---|---|---|
| Owns memory | Yes | No |
| Copy cost | O(n) | O(1) |
| Mutable | Yes | No |
| Null-terminated | Yes | Not guaranteed |
| Use case | Store and modify | Read-only function parameters |
void print(std::string_view sv) { std::cout << sv << "\n"; }
std::string s = "hello";
print(s); // no copy
print("world"); // no heap allocation
print(s.substr(0, 3)); // temporary string_view
Watch out: string_view must not outlive the underlying buffer.
Design patterns & best practices
46. What is the Pimpl idiom?
Pimpl (Pointer to Implementation) hides private implementation details behind a forward-declared opaque pointer, reducing compilation dependencies and improving ABI stability.
// widget.h
class Widget {
public:
Widget();
~Widget();
void render();
private:
struct Impl;
std::unique_ptr<Impl> pImpl_;
};
// widget.cpp
struct Widget::Impl {
// Implementation details hidden from header
int data_;
void heavy_function();
};
Widget::Widget() : pImpl_(std::make_unique<Impl>()) {}
Widget::~Widget() = default;
void Widget::render() { pImpl_->heavy_function(); }
47. What is a functor (function object)?
A functor is an object that overloads operator(), making it callable like a function. Functors carry state and can be passed to STL algorithms.
struct Multiplier {
int factor;
int operator()(int x) const { return x * factor; }
};
std::vector<int> v = {1, 2, 3};
std::transform(v.begin(), v.end(), v.begin(), Multiplier{3});
// v = {3, 6, 9}
Lambdas are syntactic sugar for anonymous functors. std::function<R(Args...)> erases the functor type.
48. What is CRTP (Curiously Recurring Template Pattern)?
CRTP provides static (compile-time) polymorphism without the vtable overhead:
template<typename Derived>
class Base {
public:
void interface() {
static_cast<Derived*>(this)->implementation();
}
};
class Concrete : public Base<Concrete> {
public:
void implementation() { std::cout << "Concrete impl\n"; }
};
Concrete c;
c.interface(); // no virtual dispatch
Used in Eigen (linear algebra), SFML, and many performance-critical libraries.
49. What is type erasure?
Type erasure hides concrete types behind a uniform interface at runtime — std::function, std::any, and std::variant are standard examples.
#include <functional>
// std::function erases the concrete callable type
std::function<int(int, int)> add = [](int a, int b){ return a + b; };
std::function<int(int, int)> mul = std::multiplies<int>{};
// std::any erases any type
#include <any>
std::any val = 42;
std::any str = std::string("hello");
50. What are common C++ anti-patterns to avoid?
| Anti-pattern | Problem | Fix |
|---|---|---|
Raw new/delete |
Leaks, exception-unsafety | Use make_unique/make_shared |
| Missing virtual destructor | UB when deleting via base pointer | Always virtual ~Base() = default |
| Passing large objects by value | Unnecessary copies | Pass by const& or move |
using namespace std; globally |
Name collisions | Qualified names or local using |
reinterpret_cast abuse |
UB, breaks strict aliasing | Use proper abstractions |
Not marking moves noexcept |
std::vector copies instead of moves |
Add noexcept to move ops |
| Signed/unsigned comparison warnings | Subtle bugs | Use size_t consistently or cast explicitly |
catch (...) with no rethrow |
Swallows exceptions silently | Log and rethrow, or handle specifically |
Common mistakes
| Mistake | Why it's wrong | Fix |
|---|---|---|
delete vs delete[] mismatch |
UB for arrays | Match new[] with delete[] |
| Dangling reference from temporary | Lifetime ended | Return by value or use const& to extend temp life |
Iterator invalidation after push_back |
vector may reallocate | Re-acquire iterators after mutation |
Double free |
UB (heap corruption) | Use smart pointers |
Forgetting override keyword |
Silently fails to override | Always use override |
shared_ptr cycle |
Memory leak | Use weak_ptr to break cycles |
| Copying instead of moving large objects | Performance loss | Use std::move for temporaries |
| UB on out-of-bounds | Crashes, exploits | Use at() or bounds checking |
C++ vs other languages
| Feature | C++ | Java | Rust | Python |
|---|---|---|---|---|
| Memory management | Manual + RAII | GC | Ownership system | GC |
| Zero-cost abstractions | Yes | Partial | Yes | No |
| Operator overloading | Yes | No | Yes | Yes |
| Templates/Generics | Templates (CT) | Generics (RT type erasure) | Generics | Duck typing |
| Concurrency primitives | thread/atomic |
synchronized/volatile |
Mutex/channels |
GIL + threading |
| Undefined behavior | Yes | No | No | No |
| Compile speed | Slow (templates) | Moderate | Slow | N/A (interpreted) |
| Main domain | Systems, games, HPC | Enterprise, Android | Systems, WASM | Data, scripting |
FAQ
Q: Should I learn C or C++ first?
A: Learn C++ directly if your goal is modern software development. C knowledge helps with embedded/OS work, but C++ is a superset and widely used in systems, games, and HPC.
Q: What is the difference between ++i and i++?
A: ++i increments and returns the new value. i++ increments and returns the old value (copies it first). For iterators and objects, ++i is preferred as i++ involves a copy.
Q: When should I use std::array instead of std::vector?
A: Use std::array when the size is known at compile time and you want stack allocation with bounds checking via .at(). std::vector is for runtime-sized, heap-allocated sequences.
Q: What is the difference between std::endl and "\n"?
A: std::endl flushes the output buffer in addition to inserting a newline — much slower in a loop. Prefer "\n" unless you specifically need to flush.
Q: How do I choose between inheritance and composition?
A: Prefer composition over inheritance (GoF principle). Use inheritance for true "is-a" relationships and when you need runtime polymorphism. Use composition for code reuse without the tight coupling of inheritance.
Q: What tools help write safer C++?
A: Enable warnings (-Wall -Wextra -Wpedantic), use sanitizers (-fsanitize=address,undefined), run static analysers (clang-tidy, Cppcheck), use smart pointers, and follow the C++ Core Guidelines.