Explore modern C++ fundamentals: types, references, functions, STL containers and algorithms, lambdas, classes, RAII, smart pointers, templates, exceptions, and resource-safe programming.
What Is C++?
C++ is a compiled language used in games, desktop software, browsers, simulation, finance, embedded systems, and other performance-sensitive work. Modern C++ emphasizes standard-library tools, value semantics, and resource-management patterns that reduce manual memory handling.
What makes modern C++ different
- Use the standard library (
std::vector,std::string, algorithms) instead of hand-written low-level code. - Use RAII and smart pointers instead of manual
newanddelete. - Use **references and
const** to avoid copies and communicate intent. - Use templates and lambdas for generic, reusable code.
- Prefer the newest practical standard your compiler supports (C++17, C++20, or C++23).
Hello, world and compiling
#include <iostream>
int main() {
std::cout << "Hello, C++!\n";
return 0;
}
// Compile and run:
// c++ -std=c++17 -Wall -Wextra -Wpedantic main.cpp -o main
// ./mainTypes, auto, and const
- Fundamental types:
int,double,bool,char,std::size_t. autolets the compiler deduce a type; use it when the type is obvious or very long.- Prefer
constfor values that should not change, andconstexprfor compile-time constants. - Use brace initialization
{}to avoid accidental narrowing conversions. std::stringandstd::string_viewreplace most rawchar*usage.
Types, auto, const, and brace initialization
#include <iostream>
#include <string>
int main() {
const int year = 2026;
constexpr double kPi = 3.14159;
auto name = std::string{"Asha"};
auto ratio = 3.0 / 4; // double
int count{5}; // brace init
// int bad{3.5}; // error: narrowing is rejected
std::cout << name << " " << year << " " << ratio << " " << count << "\n";
std::cout << "Pi ~ " << kPi << "\n";
}Functions, References, and Pointers
- Pass small values (like
int) by value. - Pass large objects by
const T&to avoid copying when the function only reads them. - Pass
T&when the function must modify the caller's object. - A reference is an alias that must refer to an existing object and cannot be reseated.
- A pointer can be null and can be reassigned; use it for optional or reseatable relationships.
- Never return a reference or pointer to a local variable.
Pass by value, const reference, and reference
#include <iostream>
#include <string>
#include <vector>
int square(int x) { return x * x; } // by value
void print_all(const std::vector<std::string>& names) { // read-only, no copy
for (const auto& name : names) std::cout << name << "\n";
}
void add_exclaim(std::string& text) { // modifies caller's object
text += "!";
}
int main() {
std::string s = "Hello";
add_exclaim(s);
std::cout << s << " " << square(7) << "\n";
print_all({"Asha", "Ravi"});
}STL Containers
The standard library provides ready-made, well-tested containers. Choose based on ordering, lookup behavior, and cost of insertion.
- **
std::vector:** dynamic array; the default choice for sequences. - **
std::array:** fixed-size array with known size at compile time. - **
std::string:** dynamic text. - **
std::map:** sorted key-value pairs with logarithmic lookup. - **
std::unordered_map:** hash table with average constant-time lookup. - **
std::set/std::unordered_set:** unique elements. - **
std::deque,std::list:** specialized sequences when you need front insertion or stable iterators.
Using vector, map, and set
#include <iostream>
#include <map>
#include <set>
#include <string>
#include <unordered_map>
#include <vector>
int main() {
std::vector<int> nums{5, 2, 9, 1};
nums.push_back(7);
for (int n : nums) std::cout << n << " ";
std::cout << "\nsize = " << nums.size() << "\n";
std::map<std::string, int> ages{{"Asha", 21}, {"Ravi", 19}};
ages["Meera"] = 24;
if (auto it = ages.find("Ravi"); it != ages.end()) {
std::cout << it->first << " is " << it->second << "\n";
}
std::unordered_map<std::string, int> fast{{"a", 1}};
std::set<int> unique{3, 1, 3, 2}; // {1, 2, 3}
std::cout << unique.size() << "\n";
}Algorithms, Iterators, and Lambdas
<algorithm>providesstd::sort,std::find_if,std::count_if,std::transform, and more.- Iterators connect containers to algorithms.
- Lambdas are inline anonymous functions:
[captures](params) { body }. - Prefer
[&]or explicit captures carefully; capturing by reference to something that dies first is a bug. - C++20 ranges provide a more readable pipeline style when available.
Sorting and searching with lambdas
#include <algorithm>
#include <iostream>
#include <numeric>
#include <string>
#include <vector>
struct Person {
std::string name;
int age;
};
int main() {
std::vector<Person> people{{"Asha", 21}, {"Ravi", 19}, {"Meera", 24}};
std::sort(people.begin(), people.end(),
[](const Person& a, const Person& b) { return a.age < b.age; });
auto adult = std::find_if(people.begin(), people.end(),
[](const Person& p) { return p.age >= 21; });
if (adult != people.end()) std::cout << adult->name << "\n";
int total = std::accumulate(people.begin(), people.end(), 0,
[](int sum, const Person& p) { return sum + p.age; });
std::cout << "Average age: " << static_cast<double>(total) / people.size() << "\n";
}Classes and Object-Oriented Design
- A class bundles data (members) with behavior (member functions).
- Use
privatedata and a small public interface. - Constructors establish a valid object; use member initializer lists.
- Mark functions that do not modify the object
const. - Rule of zero: let standard types manage resources so you need no custom destructor, copy, or move.
- Use
virtualfunctions andoverridefor runtime polymorphism; give polymorphic base classes a virtual destructor.
A class with const member functions and an interface
#include <iostream>
#include <memory>
#include <string>
#include <vector>
class BankAccount {
public:
explicit BankAccount(std::string owner, double balance = 0.0)
: owner_{std::move(owner)}, balance_{balance} {}
void deposit(double amount) {
if (amount <= 0) throw std::invalid_argument("amount must be positive");
balance_ += amount;
}
double balance() const { return balance_; }
const std::string& owner() const { return owner_; }
private:
std::string owner_;
double balance_;
};
class Shape {
public:
virtual ~Shape() = default;
virtual double area() const = 0;
};
class Circle : public Shape {
public:
explicit Circle(double r) : r_{r} {}
double area() const override { return 3.14159 * r_ * r_; }
private:
double r_;
};
int main() {
BankAccount acct{"Asha", 100};
acct.deposit(50);
std::cout << acct.owner() << ": " << acct.balance() << "\n";
std::vector<std::unique_ptr<Shape>> shapes;
shapes.push_back(std::make_unique<Circle>(2.0));
for (const auto& s : shapes) std::cout << s->area() << "\n";
}RAII and Smart Pointers
RAII (Resource Acquisition Is Initialization) ties a resource's lifetime to an object's lifetime. When the object goes out of scope, its destructor releases the resource automatically, even if an exception is thrown.
- Standard containers,
std::string,std::fstream, andstd::lock_guardare all RAII types. - **
std::unique_ptr:** single ownership; cheap and the default smart pointer. - **
std::shared_ptr:** shared ownership with reference counting; use only when ownership is truly shared. - **
std::weak_ptr:** non-owning observer that breaks reference cycles. - Create them with
std::make_uniqueandstd::make_shared. - Avoid owning raw pointers and manual
new/deleteunless there is a specific reason.
unique_ptr, shared_ptr, and a custom RAII guard
#include <iostream>
#include <memory>
struct Resource {
Resource() { std::cout << "acquired\n"; }
~Resource() { std::cout << "released\n"; }
void use() const { std::cout << "using\n"; }
};
class ScopeTimerLog {
public:
explicit ScopeTimerLog(const char* label) : label_{label} { std::cout << "start " << label_ << "\n"; }
~ScopeTimerLog() { std::cout << "end " << label_ << "\n"; }
private:
const char* label_;
};
int main() {
ScopeTimerLog log{"main"};
auto unique = std::make_unique<Resource>(); // sole owner
unique->use();
auto shared = std::make_shared<Resource>();
auto another = shared; // reference count = 2
std::cout << shared.use_count() << "\n";
std::unique_ptr<Resource> moved = std::move(unique); // ownership transfer
} // everything is released automatically hereTemplates and Generic Code
- Templates let one function or class work with many types.
- The compiler generates a version for each type used.
- C++20 concepts document and enforce requirements on template parameters.
- Standard containers and algorithms are all templates.
A function template and a class template
#include <iostream>
#include <string>
#include <vector>
template <typename T>
T maximum(const T& a, const T& b) {
return (a < b) ? b : a;
}
template <typename T>
class Stack {
public:
void push(T value) { items_.push_back(std::move(value)); }
T pop() {
T top = std::move(items_.back());
items_.pop_back();
return top;
}
bool empty() const { return items_.empty(); }
private:
std::vector<T> items_;
};
int main() {
std::cout << maximum(3, 9) << " " << maximum(std::string{"a"}, std::string{"b"}) << "\n";
Stack<int> s;
s.push(1);
s.push(2);
std::cout << s.pop() << "\n"; // 2
}Exceptions and Error Handling
- Throw exceptions for failures the current function cannot handle.
- Catch by
const&, and catch the most specific type first. - RAII ensures resources are released while the stack unwinds.
- Use
std::optionalfor "maybe a value" andstd::expected(C++23) or error codes where exceptions are not suitable. - Do not throw from destructors.
Throwing, catching, and std::optional
#include <iostream>
#include <optional>
#include <stdexcept>
#include <string>
int parse_positive(const std::string& text) {
int value = std::stoi(text); // may throw
if (value <= 0) throw std::invalid_argument("value must be positive");
return value;
}
std::optional<int> try_parse(const std::string& text) {
try {
return parse_positive(text);
} catch (const std::exception&) {
return std::nullopt;
}
}
int main() {
try {
std::cout << parse_positive("42") << "\n";
std::cout << parse_positive("-5") << "\n";
} catch (const std::invalid_argument& e) {
std::cout << "Invalid: " << e.what() << "\n";
}
if (auto v = try_parse("abc")) std::cout << *v << "\n";
else std::cout << "not a number\n";
}Move Semantics in Brief
- Copying duplicates a resource; moving transfers it cheaply, leaving the source in a valid but unspecified state.
std::movecasts to an rvalue, allowing a move; it does not move anything by itself.- Return local objects by value; the compiler optimizes or moves them automatically.
- Standard types are already movable, so the rule of zero usually gets you move support for free.
Moving instead of copying
#include <iostream>
#include <string>
#include <utility>
#include <vector>
std::vector<std::string> make_names() {
std::vector<std::string> names{"Asha", "Ravi", "Meera"};
return names; // moved/elided, not copied
}
int main() {
auto names = make_names();
std::string big(1000, 'x');
std::vector<std::string> store;
store.push_back(std::move(big)); // transfers the buffer
std::cout << store[0].size() << "\n";
}Best Practices
- Compile with
-Wall -Wextra -Wpedanticand treat warnings seriously. - Prefer
std::vectorandstd::stringover raw arrays andchar*. - Avoid owning raw pointers; use
unique_ptrby default. - Use
constand references to express intent and avoid copies. - Follow the rule of zero; if you define one special member function, think about all of them.
- Prefer algorithms and range-based
forloops over index loops. - Use sanitizers (
-fsanitize=address,undefined) and static analysis tools. - Follow the C++ Core Guidelines.
Complete Example: Counting Values with a Map
Compile with a modern compiler using C++17 or later, for example: c++ -std=c++17 -Wall -Wextra main.cpp -o main.
Count values with a map
#include <iostream>
#include <map>
#include <string>
#include <vector>
int main() {
const std::vector<std::string> languages{
"C++", "Python", "C++", "JavaScript", "Python", "C++"
};
std::map<std::string, int> counts;
for (const auto& language : languages) {
++counts[language];
}
for (const auto& [language, count] : counts) {
std::cout << language << ": " << count << '\n';
}
return 0;
}Frequently Asked Questions
What does STL mean?
STL commonly refers to the Standard Template Library concepts and facilities, including containers, iterators, and algorithms within the C++ standard library.
Should I use raw new and delete?
Prefer standard containers and smart pointers for ordinary ownership. Manual allocation is rarely the simplest choice in modern C++.
Is C++ the same as C?
No. C++ shares some syntax with C but has its own type system, abstractions, standard library, and language features.
What is RAII?
RAII ties the lifetime of a resource such as memory, a file, or a lock to the lifetime of an object, so cleanup happens automatically when the object goes out of scope.
When should I use unique_ptr versus shared_ptr?
Use unique_ptr by default for single ownership. Use shared_ptr only when several owners genuinely share responsibility for an object's lifetime.
Which C++ standard should I learn?
Learn with C++17 or later, ideally C++20, since modern features such as structured bindings, optional, and concepts make code safer and clearer.