OcxlyDev · Tutorial · Part 2 of 3

C++ intermediate: from working code to idiomatic C++

You can compile and run a basic program. Now make it C++. This part turns "it works" into "it's the right way to do it" — the standard containers, references, classes, automatic resource management, and files.

OcxlyDev Published 25 July 2026 ~26 min read Intermediate Sources linked throughout

If Part 1 was "make it run," Part 2 is "make it right." C++ gives you enormous control, and with it the responsibility to manage memory and resources yourself — but modern C++ (C++11 and later) hands you tools that do most of that management for you. The theme of this whole part is borrowed straight from the C++ Core Guidelines: prefer the standard library, prefer automatic resource management, and say what you mean.1 Every feature below links to cppreference.com2 or the Core Guidelines1 so you can read the authoritative version. Before starting, you should be comfortable with Part 1 — variables, types, cin/cout, if, loops, and functions.3

Type each example, watch what happens, and don't skip the project at the end — that's where it clicks.

01Better random numbers with <random>

Part 1 used std::rand() for the guessing game. It works, but it's low quality and now discouraged. The modern replacement is the <random> header: pick an engine (the source of randomness) and a distribution (the shape you want), and combine them.4

#include <iostream>
#include <random>

int main() {
    std::mt19937 eng{std::random_device{}()};  // seeded engine
    std::uniform_int_distribution d{1, 20};   // inclusive range 1..20

    for (int i = 0; i < 5; i = i + 1)
        std::cout << d(eng) << " ";

    return 0;
}

std::mt19937 is a well-tested engine; std::random_device seeds it from the system; the distribution maps the engine's raw output into the range you asked for.4 The Core Guidelines point you here explicitly — avoid std::rand() in new code.1

02const and references

Two ideas separate beginner C++ from idiomatic C++: const (promise not to change something) and references (an alias for an existing variable, not a copy).

A reference is written with & in a type. Pass a big object by reference to avoid copying it — and by const reference when the function only reads it:5

void shout(const std::string& text) {   // read-only alias, no copy
    std::cout << text << "!" << std::endl;
}

int n = 5;
int& r = n;            // r is another name for n
r = 10;                // n is now 10

const int c = 42;    // c can never change

const is a contract the compiler enforces. The Core Guidelines make it a default: declare everything const unless you have a reason to mutate — it documents intent and prevents whole classes of bugs.6 The const-reference parameter is the standard way to pass strings and other large objects.5

03std::string and string_view

You met std::string in Part 1. It's a growable sequence of characters with helpful methods.7

std::string s = "hello";
s += ", world";                 // append
std::cout << s.size() << " " << s.substr(0, 5);  // 11 hello

C++17 added std::string_view: a lightweight, non-owning "view" of a string's characters — perfect for function parameters that only need to read a string, because it never copies and works on substrings, literals, or std::string alike.8 Prefer std::string_view for read-only parameters when you don't need to mutate or store the text.1

void log(std::string_view msg) {   // no copy, accepts any string-like source
    std::cout << "[log] " << msg << std::endl;
}

04The standard containers

The single biggest upgrade from Part 1 is the standard library containers. You no longer manage raw arrays by hand. The workhorse is std::vector — a growable array.9

#include <vector>
std::vector<int> nums = {3, 1, 4};
nums.push_back(5);                 // append
std::cout << nums.size() << " " << nums[0];  // 4 3

A few more you'll reach for constantly:

Rule of thumb from the Core Guidelines: use std::vector as your default sequence; reach for std::array only when the size is truly fixed and known at compile time, and prefer unordered_map over map unless you need ordered iteration.1

05Range-based for and auto

Looping over a container element-by-element is so common C++ gives you a shorter loop: the range-based for. It reads "for each element x in container c."13

for (int x : nums)
    std::cout << x << " ";          // copies each element

for (const int& x : nums)     // read-only, no copy
    std::cout << x << " ";

And auto lets the compiler deduce the type — handy with iterators and long template names:14

for (const auto& x : nums)   // x's type is deduced as const int&
    std::cout << x << " ";

06Functions: overloading, defaults, inline

C++ lets you define several functions with the same name if their parameters differ — overloading. The compiler picks the best match.15 You can also give parameters default arguments so callers may omit them.16

int add(int a, int b)              { return a + b; }
double add(double a, double b)    { return a + b; }   // overload

void greet(std::string_view who, bool loud = false) {
    std::cout << "Hi, " << who << (loud ? "!!" : ".") << std::endl;
}

greet("Ada");        // Hi, Ada.
greet("Ada", true);  // Hi, Ada!!

Small, hot functions can be marked inline as a hint to the compiler to paste the body at the call site — though modern compilers decide this themselves; treat inline mainly as the way to define a function in a header without link errors.17

07enum class and struct

A plain enum leaks its names into the surrounding scope and converts implicitly to integers — a source of bugs. The fix is enum class (a "scoped enumeration"): its values are namespaced and don't convert to int silently.18 The Core Guidelines strongly prefer it.1

enum class Color { Red, Green, Blue };
Color c = Color::Green;          // must qualify the name
// int x = c;                 // error: no implicit conversion

A struct groups related data into one named type — the lightweight cousin of a class (the only difference is that struct members are public by default).19

struct Point { double x, y; };
Point p{1.0, 2.0};
std::cout << p.x << std::endl;

08Classes and objects

A class bundles data and the functions that act on it. The functions are member functions (methods); public is the interface, private is hidden state.20 A constructor builds each object; this refers to the current instance.

class Counter {
public:
    Counter() : count_{0} {}          // constructor (member init list)
    void increment() { count_ = count_ + 1; }
    int value() const { return count_; }   // const: doesn't modify the object
private:
    int count_;
};

Counter c;
c.increment();
std::cout << c.value() << std::endl;   // 1

Note the member initialiser list (: count_{0}) — the preferred way to set members, and the const on value() promising it won't change the object. Both are Core Guidelines recommendations.1

09RAII and smart pointers

This is the heart of modern C++. RAII — "Resource Acquisition Is Initialization" — means a resource (memory, file, lock) is tied to an object's lifetime: acquired in the constructor, released in the destructor. When the object goes out of scope, the resource is cleaned up automatically — even if an exception is thrown.21

For heap memory, never call new and delete by hand. Use a smart pointer: std::unique_ptr owns one object exclusively; std::shared_ptr shares ownership among several pointers.2223

#include <memory>
std::unique_ptr<Counter> p = std::make_unique<Counter>();
p->increment();                       // use -> to call through a pointer
// no delete needed: p frees the Counter when it leaves scope

The Core Guidelines are blunt here: "don't use explicit new, delete, or owning raw pointers"1 — use std::make_unique / std::make_shared. This single habit eliminates the vast majority of C++ memory bugs.

10File input and output

Programs that only talk to the screen are limited. C++ reads and writes files with <fstream>: std::ofstream to write, std::ifstream to read.2425 Because streams are RAII objects, the file closes itself when they go out of scope.

#include <fstream>
#include <string>

{   // write
    std::ofstream out{"notes.txt"};
    out << "first line\n";
}   // file closed automatically here

{   // read line by line
    std::ifstream in{"notes.txt"};
    std::string line;
    while (std::getline(in, line))
        std::cout << line << std::endl;
}

11Project: a tiny contact book

Let's combine containers, classes, files, and strings into something real: a contact book that loads names from a file, stores them in a vector, and saves changes back.

#include <iostream>
#include <fstream>
#include <vector>
#include <string>

int main() {
    const std::string file = "contacts.txt";
    std::vector<std::string> contacts;
    std::string line;

    {   // load existing contacts
        std::ifstream in{file};
        while (std::getline(in, line))
            if (!line.empty()) contacts.push_back(line);
    }

    // add a new one from the user
    std::cout << "Add a contact name: ";
    std::getline(std::cin, line);
    contacts.push_back(line);

    {   // save everything back
        std::ofstream out{file};
        for (const auto& c : contacts) out << c << "\n";
    }

    std::cout << "Saved " << contacts.size() << " contacts.\n";
    return 0;
}

Every piece is from this part: a vector of strings, const where we don't mutate, std::getline for input, RAII file streams that close themselves, and range-based for with const auto&. That's a real, persistent program — and you understand all of it.

12Where to go next

You've gone from "it compiles" to genuinely idiomatic C++: the standard containers, references and const, enum class, classes with constructors, RAII and smart pointers, and file I/O. These are the daily tools of working C++ code.

About this piece. This is Part 2 (Intermediate) of a three-part C++ series from OcxlyDev — preceded by Part 1: Basics and followed by Part 3: Advanced. Every language feature links to a reputable primary source below (cppreference.com, the C++ Core Guidelines, and learncpp.com). Ready for the next step? Continue with the advanced tutorial, where we go under the hood: templates, the rule of zero/five, move semantics, lambdas, and concurrency.

References

  1. Bjarne Stroustrup & Herb Sutter — C++ Core Guidelines (community best-practice rules; P.1, F.1, R.1, ES.28, C.46, NR.1)
  2. cppreference.com — C++ reference (definitive language and standard-library documentation)
  3. learncpp.com — free, modern C++ tutorial (recommended starting course; covers basics through intermediate)
  4. cppreference.com — Pseudo-random number generation (<random>, std::mt19937, std::random_device, distributions)
  5. cppreference.com — Reference declaration (lvalue references, including const reference parameters)
  6. cppreference.com — cv (const and volatile qualifiers; const as a compile-time contract)
  7. cppreference.com — std::basic_string (the std::string class and its methods)
  8. cppreference.com — std::basic_string_view (non-owning read-only string view, since C++17)
  9. cppreference.com — std::vector (a dynamically growable array; push_back, size, operator[])
  10. cppreference.com — std::array (fixed-size contiguous container; safer than a raw C array)
  11. cppreference.com — std::map (ordered associative container, key→value, O(log n))
  12. cppreference.com — std::unordered_map (hash-based associative container, average O(1))
  13. cppreference.com — Range-based for loop (iterating over a container)
  14. cppreference.com — auto specifier (type deduction, since C++11)
  15. cppreference.com — Overload resolution (function overloading and best-match selection)
  16. cppreference.com — Default arguments (omittable function parameters)
  17. cppreference.com — inline specifier (function inlining and header definitions)
  18. cppreference.com — Enumeration declaration (enum class / scoped enumerations)
  19. cppreference.com — Classes (member functions, access control, constructors)
  20. cppreference.com — Classes (public/private, this, member functions)
  21. cppreference.com — RAII (Resource Acquisition Is Initialization; automatic cleanup via scope)
  22. cppreference.com — std::unique_ptr (exclusive-ownership smart pointer; std::make_unique)
  23. cppreference.com — std::shared_ptr (shared-ownership smart pointer; std::make_shared)
  24. cppreference.com — std::basic_ofstream (output file stream; writes to a file)
  25. cppreference.com — std::basic_ifstream (input file stream; reads from a file)