Modern C++
"Modern C++" typically refers to the series of coding practices and library tools introduced in the C++11 standard and subsequent versions. It does not completely discard old C++, but instead builds upon existing syntax to provide safer, clearer, and more engineering-friendly ways of expression.
This chapter is organized in a progression from foundational to advanced concepts: beginning with syntax commonly used in daily practice, moving on to lifecycle and resource management, then covering callbacks, data representation, time, files, and concurrency, and finally introducing modular compilation.
Conventions for the Tutorial Examples in This Chapter
For your convenience in directly copying and verifying, the "sample code" in each section of this chapter adheres to these conventions:
- The example code includes the complete
#include. - All example code includes the
mainfunction. - Example code can be copied into a single
.cppfile for compilation and execution. - The running result is given below each example code.
- For examples, try to introduce only one new concept at a time, avoiding cramming too much into a single example.
The "common errors" mentioned in the text are used to illustrate what not to do. To avoid accidental copying, incorrect examples will be尽量 placed within text descriptions or short snippets, not presented as runnable examples.
Learning Sequence
| Stage | the first problem to solve | Corresponding Chapter |
|---|---|---|
| Basic Expressions | Write fewer redundant types, minimize null pointer and enum misuse, and make traversals clearer. | auto, nullptr, using, enum class, range-based for, structured binding |
| lifecycle | When are resources released, who owns the objects, and how are large objects passed efficiently? | constexpr, RAII, smart pointers, rvalue references, and move semantics |
| Callbacks and Data Models | small functions, callback preservation, parameter adaptation, optional value, one of several types | Lambda、std::function、std::bind、std::optional、std::variant、std::span |
| System capabilities | Formatted output, timing, concurrency, file system, modularity | std::format / std::print, std::chrono, concurrent programming, std::filesystem, modules |
The focus of modern C++ is not "syntax updates" but "making the intent clear". For example:
nullptrkeep null pointers and integers0separate.enum classprevents arbitrary mixing of different enum types.- RAII and smart pointers delegate resource release to object lifecycle.
- Lambda allows local callbacks to be written at the point of use.
- Unify the storage and passing of different types of callable objects.
- Make formatted output more concise while maintaining type safety.
The Relationship Between Old and Modern Approaches
Many modern C++ features stem from a simple problem: the old ways of doing things can work, but they tend to become error-prone in complex scenarios.
| Old way of writing | Main Issues | Modern Writing Style |
|---|---|---|
NULL / 0 indicates a null pointer. | can be confused with integer overloading | nullptr |
typedef write type alias | Template aliases are not intuitive. | using |
| Ordinary enumeration | Enum names pollute the scope, can implicitly convert to integers. | enum class |
Manual new/delete | Early returns, exceptions, and repeated releases are error-prone. | RAII、std::unique_ptr、std::shared_ptr |
Manual lock/unlock | Forgetting to unlock will cause deadlock, and exceptional paths are even more dangerous. | std::lock_guard、std::unique_lock |
| Defining ordinary functions for simple callbacks at a distance. | Local logic gets scattered, making it inconvenient to carry context. | Lambda |
| Only use function pointers to save callbacks | Cannot save captured lambdas and function objects | std::function |
| manually adjust the adaptation function parameters | Code duplication, troublesome adaptation of old interfaces | std::bind or Lambda |
| Represent failure using a special value. | -1, empty strings, and other magic values have unclear semantics. | std::optional |
Use union to denote various types. | Accessing an invalid type results in undefined behavior. | std::variant |
| Handwriting time unit conversion | Seconds, milliseconds, and microseconds can be easy to confuse. Here's a quick breakdown: |
- Second (s) – The basic unit of time.
- Millisecond (ms) – One-thousandth of a second (1 ms = 0.001 s).
- Microsecond (μs) – One-millionth of a second (1 μs = 0.000001 s).
Common Examples:
- A heartbeat ≈ 800 ms
- A camera flash ≈ 1 ms
- CPU clock cycles ≈ tens of nanoseconds (0.001 μs)
Easy Mnemonic:
“Milli- means thousandths, micro- means millionths.”|std::chrono|
|Platform-related file API|The syntax differs between Windows and Linux.|std::filesystem|
When learning about each feature, you can understand it by considering three questions:
- What are the common pitfalls of the old writing style?
- How do modern approaches encode intent into types or syntax?
- The differences become noticeable in scenarios where a deep understanding of the underlying hardware or system architecture is required, such as in advanced robotics projects using the Robot Operating System or when programming a microcontroller for precise electrical control. For beginners following a tutorial, the distinctions might seem subtle initially, but as one delves into more complex applications—like integrating vision systems or customizing sensor behaviors—the variations in approach and terminology become significant. Referencing detailed blog posts and comprehensive references can further clarify these practical differences.
For instance, with a smart pointer example, if you only create the object inside main and then exit normally, it seems much the same as manually delete; but once there's an early return, an exception, or cross‑function passing, the value of RAII becomes immediately apparent. Similarly, with a Lambda, if you just invoke it right away, capturing by value or by reference might both work fine; but once it's saved into a callback, a thread, or a timer, the difference in lifecycles becomes critically important.
Recommended Learning Path
Make the code clearer.
Start by learning auto, nullptr, using, enum class, range-for loops, and structured bindings. These concepts are not difficult, but they can significantly reduce verbose code and low-level errors.
Key points to remember for this stage:
autois not weakly typed; it just lets the compiler infer types for you.nullptrrepresents a null pointer only, not an integer.enum classdoes not mix with integers by default.- Range-for defaults to using
const auto&to iterate over large objects. - Structured bindings are suitable for unpacking
pair,tuple, structs, andmapelements.
understand lifecycle
Continue learning constexpr, RAII, smart pointers, and move semantics. At this stage, more important than the syntax is the mindset: when is an object created, when is it destroyed, who owns it, and who is just borrowing it.
It is recommended to understand in this order:
- RAII: Resource and Object Lifecycle Binding.
unique_ptr:Exclusive ownership, default option.shared_ptrshould only be used when multiple owners are indeed required.weak_ptr: Only observe without extending the lifecycle, to break circular references.- Move semantics: Transfer resources out to avoid deep copying of large objects.
Master callbacks and data expressions
The three chapters on Lambda, std::function, and std::bind should be read consecutively but not memorized together.
| Tools | Problems Addressed | Key points |
|---|---|---|
| Lambda | Write a small function in the field. | capture lists, parameters, return values, lifetime |
std::function | Uniformly store and pass different types of callable objects. | Type erasure, callback member variables, callback containers |
std::bind | Adapting parameters of existing functions | Fixing parameters, adjusting order, binding member functions |
std::optional, std::variant, and std::span respectively address the issues of "might not have a value," "one of several types," and "borrowing a contiguous block of data."
Developing Engineering Competence
Finally, learn about formatting output, time, concurrency, filesystems, and modules. These topics are closer to real-world projects:
std::format/std::printis more concise thanstringstream, and more type-safe thanprintf.- Avoid manual time unit conversions.
- Concurrent programming should prioritize correctness before speed.
- Make path, directory, and file information handling cross-platform.
- Modules, a new compilation model introduced in C++20, should first be understood conceptually, with adoption decided based on toolchain support.
Practical Exercise Suggestions
- In the tutorial: write two overloaded functions, respectively receiving
intandint*, observe the differences between0,NULL, andnullptr. - Learning
enum class: Replace ordinary enums with enum classes, and see which implicit conversions are forbidden. - When learning smart pointers, first write a manual
returnexample ofnew/delete, then convert it tounique_ptr. - Learning Lambda: Use value capture and reference capture separately to observe whether external variables change.
When using value capture, a copy of the variable is made, so the original remains unchanged. With reference capture, the variable is accessed directly by reference, allowing modifications to the original.
5. Learn std::function: Put multiple different lambdas into the same vector and execute them uniformly.
6. Learning concurrency: First sequentially execute two 1-second wait tasks, then run them in two threads, comparing the time taken.