What “modern C++” means
C++ has published language editions such as C++11, C++14, C++17, C++20 and C++23. A standard specifies the language and library facilities; it is not an application you install. Your compiler and standard-library implementation provide those facilities, and their support may differ by version, platform and feature. Select a language mode explicitly for a tutorial or project rather than relying on the compiler default.
This guide uses C++17 for every complete example. The main techniques here are older: auto type deduction, range-based for and standard smart pointers arrived in C++11; std::make_unique arrived in C++14; std::string, std::vector and references predate them. C++17 is a convenient common baseline, not a claim that these features began in 2017. Check your toolchain’s feature and library support before using newer facilities. Draft proposals and features still being standardised are not portable promises.
GCC or Clang: select the language mode
g++ -std=c++17 main.cpp -o main
# or: clang++ -std=c++17 main.cpp -o mainUse library types for everyday data
std::string represents a text string and manages its storage. std::vector<T> is a growable sequence of values of type T. The example uses a vector of strings for lesson names. Include <string> and <vector> explicitly rather than hoping another header happens to include them. These types help you work with text and collections without manually allocating character buffers or dynamic arrays.
The const keyword means these values cannot be changed through their names in this scope. The braces initialise the values. std::vector still uses memory internally, but it owns and releases that storage for you. A vector can grow; references or pointers to its elements may be invalidated by some growth operations, so do not keep such references across modifications without checking the relevant rules.
Read the loop one piece at a time
for (const auto& topic : topics) visits each element in the vector. This range-based for loop avoids an index when you only need each value. Here auto asks the compiler to infer the element type, std::string, from topics; it does not make the program dynamically typed. The ampersand creates a reference, an alias to the existing string rather than a copy. const prevents changing that string through topic.
For a small int, copying with for (int score : scores) is often simpler. A const reference becomes more useful for larger objects such as strings. A reference must refer to an existing object and must not outlive it. In the example the reference is used only inside the loop, while the vector remains alive.
Let objects manage resources
Resource Acquisition Is Initialization (RAII) ties ownership of a resource to an object’s lifetime. When that owner leaves scope, its destructor releases the resource even when an early return or exception interrupts normal flow. std::string and std::vector manage their allocated storage this way. File streams use the same idea for file handles.
The optional local-file example opens progress.txt, checks that opening succeeded and writes a line. The ofstream object closes its file when it leaves main; you do not need an explicit close just to prevent a leak. Successful opening does not guarantee every later write succeeds, so a production application that needs durable output should check the stream and handle I/O failures. This example is for a local toolchain; the Codectionary Playground does not promise persistent files.
Local main.cpp: a file stream owns its file handle
#include <fstream>
#include <iostream>
#include <string>
int main() {
std::ofstream report{"progress.txt"};
if (!report) {
std::cerr << "Could not open progress.txt\n";
return 1;
}
report << "C++ practice complete\n";
}Use smart pointers when ownership must be indirect
Prefer an ordinary local object or a value returned by a function when possible. If an object must be dynamically allocated and ownership passed between parts of a program, std::unique_ptr<T> expresses one owner and destroys the object when that owner ends. std::make_unique<T> constructs that owner. std::shared_ptr<T> permits shared ownership, but its extra lifetime complexity and cost are not a reason to use it by default.
The ownership-transfer example passes a unique_ptr to display with std::move. That transfers ownership; main must not use note as if it still owned the object. The example exists to teach transfer of ownership, not to suggest that every Note needs heap allocation. Raw pointers and references remain useful for non-owning access. A raw owning pointer plus manual new/delete should not be the beginner default because cleanup can be skipped on error paths.
C++17: explicit transfer of unique ownership
#include <iostream>
#include <memory>
#include <string>
#include <utility>
struct Note {
std::string text;
};
void display(std::unique_ptr<Note> note) {
std::cout << note->text << '\n';
}
int main() {
auto note = std::make_unique<Note>(Note{"Review this lesson"});
display(std::move(note));
}Learn modern fundamentals without losing the basics
You still need to understand expressions, types, functions, scope, compilation and error messages. “Modern” does not mean avoiding those fundamentals: it means choosing clear language and library facilities that express ownership and intent. First build and change the string-and-vector example. Then compile the file-stream example locally and inspect the created file. Finally trace who owns the Note before and after std::move.
When reading older tutorials, distinguish code shown to explain historical low-level mechanisms from code recommended for a new program. Check the required standard and the compiler’s diagnostics before copying a feature into your project.
Syntax
#include <iostream>
#include <string>
#include <vector>
int main() {
const std::string learner{"James"};
const std::vector<std::string> topics{"strings", "vectors", "loops"};
for (const auto& topic : topics) {
std::cout << learner << " is learning " << topic << '\n';
}
}Examples
Print a learning plan with strings and a vector
A complete C++17 program using standard library values and a range-based loop.
#include <iostream>
#include <string>
#include <vector>
int main() {
const std::string learner{"James"};
const std::vector<std::string> topics{"strings", "vectors", "loops"};
for (const auto& topic : topics) {
std::cout << learner << " is learning " << topic << '\n';
}
}- The program stores one learner name and three topic names, then prints one line for each topic.
- #include makes the stream, string and vector declarations available. The vector owns the sequence of strings.
- const auto& topic refers to each stored string without copying it and without permitting this loop to change it.
- Expected output is three lines beginning “James is learning”, followed by strings, vectors and loops respectively. Change the vector to see the loop adapt without changing its bounds.
Best practices
- Choose and record a C++ language mode for each project; verify both compiler and library support.
- Use values and standard containers first; introduce explicit dynamic ownership only when needed.
- Keep ownership clear, and prefer RAII to manual cleanup on every exit path.
Runtime & versions
Compiled native applications. Feature availability depends on the language and runtime version used by your project; consult the official reference below.
ISO C++ resources