C++smart_pointers.cpp
Smart pointers: unique_ptr and shared_ptr
#include <memory>
#include <iostream>
struct Widget {
int id;
Widget(int i) : id(i) { std::cout << "Widget " << id << " created\n"; }
~Widget() { std::cout << "Widget " << id << " destroyed\n"; }
void use() { std::cout << "Using " << id << "\n"; }
};
int main() {
// unique_ptr: sole ownership, deleted when out of scope
auto w1 = std::make_unique<Widget>(1);
w1->use();
// auto w2 = w1; // COMPILE ERROR: can't copy unique_ptr
auto w2 = std::move(w1); // transfer ownership — w1 is now nullptr
// shared_ptr: shared ownership via atomic reference count
auto s1 = std::make_shared<Widget>(2);
{
auto s2 = s1; // both own Widget 2 — ref count = 2
std::cout << "count: " << s1.use_count() << "\n"; // 2
} // s2 destroyed — ref count = 1
std::cout << "count: " << s1.use_count() << "\n"; // 1
// weak_ptr: observe without owning — breaks circular refs
std::weak_ptr<Widget> weak = s1;
if (auto locked = weak.lock()) { // try to get shared_ptr
locked->use();
}
s1.reset(); // Widget 2 destroyed here
std::cout << "expired: " << weak.expired() << "\n"; // 1
}C++raii.cpp
RAII: resource management via destructors
#include <fstream>
#include <mutex>
#include <iostream>
#include <stdexcept>
// RAII file handle: closes even if exception thrown
class FileWriter {
std::ofstream file;
public:
explicit FileWriter(const std::string& path) : file(path) {
if (!file) throw std::runtime_error("Cannot open: " + path);
}
~FileWriter() { file.close(); } // always runs — exception-safe
void write(const std::string& text) { file << text; }
};
// RAII mutex lock — same idea as std::lock_guard
class ScopedLock {
std::mutex& m;
public:
explicit ScopedLock(std::mutex& mutex) : m(mutex) { m.lock(); }
~ScopedLock() { m.unlock(); }
};
// Modern: use std::lock_guard (same pattern, from stdlib)
std::mutex mtx;
void thread_safe_op() {
std::lock_guard<std::mutex> guard(mtx); // locked
// ... critical section
} // guard destroyed — mutex unlocked automatically
// scoped_lock for multiple mutexes (deadlock-free)
std::mutex m1, m2;
void multi_lock() {
std::scoped_lock lock(m1, m2); // locks both atomically
}C++stl_algorithms.cpp
STL algorithms: sort, find, transform, accumulate
#include <algorithm>
#include <numeric>
#include <vector>
#include <string>
#include <iostream>
int main() {
std::vector<int> v{5, 3, 8, 1, 9, 2, 7, 4, 6};
std::sort(v.begin(), v.end()); // {1,2,3,4,5,6,7,8,9}
std::sort(v.begin(), v.end(), std::greater<int>{}); // descending
// Binary search (requires sorted range)
bool found = std::binary_search(v.begin(), v.end(), 5);
// find_if: returns iterator to first match
auto it = std::find_if(v.begin(), v.end(), [](int x){ return x > 5; });
if (it != v.end()) std::cout << "First >5: " << *it << "\n";
// count_if
int odds = std::count_if(v.begin(), v.end(), [](int x){ return x % 2 != 0; });
// transform: apply function to each element
std::vector<int> doubled(v.size());
std::transform(v.begin(), v.end(), doubled.begin(), [](int x){ return x * 2; });
// accumulate: fold to single value
int sum = std::accumulate(v.begin(), v.end(), 0);
int product = std::accumulate(v.begin(), v.end(), 1, std::multiplies<int>{});
// partition: split in-place by predicate
auto mid = std::partition(v.begin(), v.end(), [](int x){ return x % 2 == 0; });
// elements before mid are even, after are odd
// remove_if + erase (erase-remove idiom)
v.erase(std::remove_if(v.begin(), v.end(), [](int x){ return x > 7; }), v.end());
std::cout << "sum=" << sum << " product=" << product << "\n";
}C++lambdas.cpp
Lambdas: captures, mutable, and generic
#include <functional>
#include <iostream>
#include <vector>
int main() {
int base = 10;
// [=] capture by value, [&] capture by reference
auto add_base = [base](int x) { return x + base; }; // captures base by value
auto modify = [&base]() { base *= 2; }; // captures by reference
std::cout << add_base(5) << "\n"; // 15
modify();
std::cout << base << "\n"; // 20
// mutable: allow modifying value-captured variables
int counter = 0;
auto count = [counter]() mutable { return ++counter; };
std::cout << count() << count() << "\n"; // 1 2
std::cout << counter << "\n"; // 0 — original unchanged
// Generic lambda (auto parameter) — C++14
auto identity = [](auto x) { return x; };
std::cout << identity(42) << " " << identity("hello") << "\n";
// Lambda as callback stored in std::function
std::function<int(int)> square = [](int x) { return x * x; };
// Immediately invoked lambda expression (IILE)
const auto result = [&base]() {
// complex initialisation logic
return base * base;
}(); // called immediately
// Capture by move (C++14)
auto large_data = std::vector<int>(1000, 42);
auto process = [data = std::move(large_data)]() {
return data.size(); // data moved into lambda
};
std::cout << process() << "\n"; // 1000
}C++optional_variant.cpp
std::optional and std::variant (C++17)
#include <optional>
#include <variant>
#include <string>
#include <iostream>
std::optional<int> safe_divide(int a, int b) {
if (b == 0) return std::nullopt;
return a / b;
}
int main() {
auto result = safe_divide(10, 3);
if (result) {
std::cout << "result: " << *result << "\n"; // 3
}
std::cout << result.value_or(-1) << "\n"; // 3
auto bad = safe_divide(5, 0);
std::cout << bad.value_or(-1) << "\n"; // -1
// variant: type-safe union — holds exactly one of its types
using Value = std::variant<int, double, std::string>;
Value v = 42;
v = 3.14;
v = std::string("hello");
// Visit: pattern match all types
std::visit([](auto&& arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, int>)
std::cout << "int: " << arg << "\n";
else if constexpr (std::is_same_v<T, double>)
std::cout << "double: " << arg << "\n";
else
std::cout << "string: " << arg << "\n";
}, v);
// std::get — throws std::bad_variant_access if wrong type
if (std::holds_alternative<std::string>(v)) {
std::cout << std::get<std::string>(v) << "\n"; // "hello"
}
}C++move_semantics.cpp
Move semantics and rule of five
#include <iostream>
#include <utility>
#include <vector>
class Buffer {
int* data;
size_t size;
public:
explicit Buffer(size_t n) : data(new int[n]()), size(n) {
std::cout << "construct " << n << "\n";
}
~Buffer() { delete[] data; std::cout << "destroy\n"; }
// Copy constructor: deep copy
Buffer(const Buffer& o) : data(new int[o.size]), size(o.size) {
std::copy(o.data, o.data + size, data);
std::cout << "copy\n";
}
// Move constructor: steal resources — noexcept enables vector optimisation
Buffer(Buffer&& o) noexcept : data(o.data), size(o.size) {
o.data = nullptr; o.size = 0; // leave source in valid empty state
std::cout << "move\n";
}
// Copy and move assignment omitted for brevity — follow same pattern
Buffer& operator=(const Buffer&) = delete;
Buffer& operator=(Buffer&&) = delete;
size_t getSize() const { return size; }
};
int main() {
Buffer b1(100);
Buffer b2 = std::move(b1); // "move" printed — O(1), not O(n)
std::cout << "b2 size: " << b2.getSize() << "\n"; // 100
std::cout << "b1 size: " << b1.getSize() << "\n"; // 0
// emplace_back: constructs in-place, avoids copy/move
std::vector<Buffer> buffers;
buffers.reserve(3);
buffers.emplace_back(50); // constructs directly — no move needed
buffers.emplace_back(100);
}C++ranges.cpp
C++20 ranges and views
#include <ranges>
#include <vector>
#include <iostream>
#include <string>
int main() {
std::vector<int> v{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// Range pipeline: lazy, composable — no intermediate containers
auto result = v
| std::views::filter([](int x){ return x % 2 == 0; })
| std::views::transform([](int x){ return x * x; })
| std::views::take(3);
for (int x : result) std::cout << x << " "; // 4 16 36
std::cout << "\n";
// std::ranges::sort: cleaner than sort(v.begin(), v.end())
std::ranges::sort(v);
// iota: generate sequence lazily
for (int i : std::views::iota(1, 6)) std::cout << i << " "; // 1 2 3 4 5
// zip (C++23)
// std::vector names{"Alice","Bob","Carol"};
// std::vector scores{95,87,92};
// for (auto [name,score] : std::views::zip(names, scores))
// std::cout << name << ": " << score << "\n";
// Reverse view
for (int x : v | std::views::reverse | std::views::take(3)) {
std::cout << x << " "; // 10 9 8
}
// split view: tokenise
std::string csv = "one,two,three,four";
for (auto part : csv | std::views::split(',')) {
std::cout << std::string(part.begin(), part.end()) << "\n";
}
}C++structured_bindings.cpp
Structured bindings and if constexpr (C++17)
#include <map>
#include <tuple>
#include <string>
#include <iostream>
#include <type_traits>
struct Point { double x, y, z; };
int main() {
// Structured bindings: unpack pairs, tuples, and structs
auto [a, b] = std::make_pair(1, "hello");
auto [x, y, z] = Point{1.0, 2.0, 3.0};
std::map<std::string, int> scores{{"Alice", 95}, {"Bob", 87}};
for (const auto& [name, score] : scores) {
std::cout << name << ": " << score << "\n";
}
// Map insert returns {iterator, bool} — use structured binding
auto [it, inserted] = scores.insert({"Carol", 92});
std::cout << "inserted: " << inserted << "\n";
// if constexpr: compile-time branching in templates
auto describe = []<typename T>(T val) {
if constexpr (std::is_integral_v<T>)
return std::string("integer: ") + std::to_string(val);
else if constexpr (std::is_floating_point_v<T>)
return std::string("float: ") + std::to_string(val);
else
return std::string("other");
};
std::cout << describe(42) << "\n"; // integer: 42
std::cout << describe(3.14) << "\n"; // float: 3.140000
}C++concepts.cpp
C++20 concepts
#include <concepts>
#include <iostream>
// Named concept: constraint on template parameter
template<typename T>
concept Numeric = std::integral<T> || std::floating_point<T>;
template<typename T>
concept Printable = requires(T t) {
{ std::cout << t } -> std::same_as<std::ostream&>;
};
// Use concept as constraint
template<Numeric T>
T square(T x) { return x * x; }
// requires clause: inline constraint
template<typename T>
requires std::totally_ordered<T>
T clamp(T val, T lo, T hi) {
return (val < lo) ? lo : (val > hi) ? hi : val;
}
// Abbreviated template syntax (C++20)
auto add(Numeric auto a, Numeric auto b) { return a + b; }
// Concept with compound requirements
template<typename T>
concept Container = requires(T c) {
{ c.begin() };
{ c.end() };
{ c.size() } -> std::convertible_to<std::size_t>;
typename T::value_type;
};
template<Container C>
void print_container(const C& c) {
for (const auto& elem : c) std::cout << elem << " ";
std::cout << "\n";
}
int main() {
std::cout << square(5) << "\n"; // 25
std::cout << square(3.14) << "\n"; // 9.8596
// square("hello"); // clear error: constraint not satisfied
std::cout << clamp(15, 0, 10) << "\n"; // 10
}C++templates.cpp
Function and class templates
#include <iostream>
#include <vector>
#include <stdexcept>
// Function template with multiple parameters
template<typename T, typename U>
auto add(T a, U b) -> decltype(a + b) { return a + b; }
// Class template
template<typename T>
class Stack {
std::vector<T> items;
public:
void push(T item) { items.push_back(std::move(item)); }
T pop() {
if (empty()) throw std::underflow_error("stack is empty");
T top = std::move(items.back());
items.pop_back();
return top;
}
const T& peek() const {
if (empty()) throw std::underflow_error("stack is empty");
return items.back();
}
bool empty() const { return items.empty(); }
size_t size() const { return items.size(); }
};
// Template specialisation for const char* — special comparison
template<>
class Stack<const char*> {
// Different implementation for C-strings
};
// Variadic templates (C++11): any number of arguments
template<typename... Args>
void print_all(Args&&... args) {
((std::cout << args << " "), ...); // fold expression
std::cout << "\n";
}
int main() {
Stack<int> s;
s.push(1); s.push(2); s.push(3);
std::cout << s.pop() << "\n"; // 3
std::cout << s.peek() << "\n"; // 2
print_all(1, 2.0, "hello", true); // 1 2 hello 1
}C++string_format.cpp
std::string and std::format (C++20)
#include <string>
#include <sstream>
#include <format> // C++20
#include <iostream>
#include <algorithm>
int main() {
std::string s = "Hello, World!";
// Substring, find, replace
std::cout << s.substr(7, 5) << "\n"; // World
auto pos = s.find("World");
if (pos != std::string::npos) s.replace(pos, 5, "C++");
std::cout << s << "\n"; // Hello, C++!
// Case conversion
std::string upper = s;
std::transform(upper.begin(), upper.end(), upper.begin(), ::toupper);
// String building with ostringstream
std::ostringstream oss;
for (int i = 0; i < 5; ++i) oss << i << (i < 4 ? "," : "");
std::string csv = oss.str(); // "0,1,2,3,4"
// std::format (C++20): type-safe printf
auto msg = std::format("Pi ≈ {:.4f}, count = {:05d}", 3.14159, 42);
std::cout << msg << "\n"; // "Pi ≈ 3.1416, count = 00042"
// Named arguments (C++23)
// auto s2 = std::format("{name} is {age}", std::arg("name","Alice"), std::arg("age",30));
// string_view: non-owning reference — avoids copies
std::string_view sv = s;
std::cout << sv.substr(0, 5) << "\n"; // Hello
}C++concurrency.cpp
std::thread, atomic, and futures
#include <thread>
#include <atomic>
#include <future>
#include <vector>
#include <iostream>
int main() {
// std::atomic: thread-safe operations without mutex
std::atomic<int> counter{0};
std::vector<std::thread> threads;
for (int i = 0; i < 10; ++i) {
threads.emplace_back([&counter]() {
counter.fetch_add(1, std::memory_order_relaxed);
});
}
for (auto& t : threads) t.join();
std::cout << "counter: " << counter << "\n"; // 10
// std::async: run function asynchronously
auto future = std::async(std::launch::async, []() {
// runs in a thread pool thread
return 42;
});
std::cout << "result: " << future.get() << "\n"; // 42 (blocks if not ready)
// std::promise / std::future: manual control
std::promise<std::string> promise;
std::future<std::string> fut = promise.get_future();
std::thread producer([&promise]() {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
promise.set_value("result from thread");
});
std::cout << fut.get() << "\n"; // blocks until producer sets value
producer.join();
}C++containers.cpp
STL containers: vector, map, unordered_map
#include <vector>
#include <map>
#include <unordered_map>
#include <set>
#include <iostream>
int main() {
// vector: prefer for most sequences
std::vector<int> v{3, 1, 4, 1, 5, 9};
v.reserve(20); // pre-allocate to avoid reallocations
v.emplace_back(2); // construct in-place (no copy/move overhead)
v.erase(v.begin() + 1); // erase at index 1
// map: balanced BST — O(log n), sorted by key
std::map<std::string, int> m;
m["alice"] = 95;
m.emplace("bob", 87); // emplace: construct in-place
m.try_emplace("alice", 100); // does NOT overwrite if key exists
std::cout << m["alice"] << "\n"; // 95
// unordered_map: hash table — O(1) average
std::unordered_map<std::string, int> um;
um.reserve(100); // set expected size to avoid rehashing
// Safe lookup — [] inserts default if missing
auto it = um.find("missing");
if (it != um.end()) { /* found */ }
// um["missing"] would create it with value 0
// Frequency count
std::vector<std::string> words{"go","go","rust","cpp","go"};
std::unordered_map<std::string, int> freq;
for (const auto& w : words) freq[w]++;
std::cout << freq["go"] << "\n"; // 3
// set: unique sorted elements
std::set<int> s{3,1,4,1,5,9,2,6};
std::cout << s.size() << "\n"; // 7 (duplicate 1 removed)
}C++ reference — C++ overview · Learn C++
Everything C++ in one place — learning paths, reference, playground, and more.
C++ Hub →