第 18 節

Modern C++

0瀏覽次數0訪問次數--跳出率--平均停留

"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:

  1. The example code includes the complete #include.
  2. All example code includes the main function.
  3. Example code can be copied into a single .cpp file for compilation and execution.
  4. The running result is given below each example code.
  5. 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

Stagethe first problem to solveCorresponding Chapter
Basic ExpressionsWrite fewer redundant types, minimize null pointer and enum misuse, and make traversals clearer.auto, nullptr, using, enum class, range-based for, structured binding
lifecycleWhen 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 Modelssmall functions, callback preservation, parameter adaptation, optional value, one of several typesLambda、std::functionstd::bindstd::optionalstd::variantstd::span
System capabilitiesFormatted output, timing, concurrency, file system, modularitystd::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:

  • nullptr keep null pointers and integers 0 separate.
  • enum class prevents 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 writingMain IssuesModern Writing Style
NULL / 0 indicates a null pointer.can be confused with integer overloadingnullptr
typedef write type aliasTemplate aliases are not intuitive.using
Ordinary enumerationEnum names pollute the scope, can implicitly convert to integers.enum class
Manual new/deleteEarly returns, exceptions, and repeated releases are error-prone.RAII、std::unique_ptrstd::shared_ptr
Manual lock/unlockForgetting to unlock will cause deadlock, and exceptional paths are even more dangerous.std::lock_guardstd::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 callbacksCannot save captured lambdas and function objectsstd::function
manually adjust the adaptation function parametersCode duplication, troublesome adaptation of old interfacesstd::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 conversionSeconds, 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:

  1. What are the common pitfalls of the old writing style?
  2. How do modern approaches encode intent into types or syntax?
  3. 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.

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:

  • auto is not weakly typed; it just lets the compiler infer types for you.
  • nullptr represents a null pointer only, not an integer.
  • enum class does 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, and map elements.

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:

  1. RAII: Resource and Object Lifecycle Binding.
  2. unique_ptr:Exclusive ownership, default option.
  3. shared_ptr should only be used when multiple owners are indeed required.
  4. weak_ptr: Only observe without extending the lifecycle, to break circular references.
  5. 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.

ToolsProblems AddressedKey points
LambdaWrite a small function in the field.capture lists, parameters, return values, lifetime
std::functionUniformly store and pass different types of callable objects.Type erasure, callback member variables, callback containers
std::bindAdapting parameters of existing functionsFixing 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::print is more concise than stringstream, and more type-safe than printf.
  • 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

  1. In the tutorial: write two overloaded functions, respectively receiving int and int*, observe the differences between 0, NULL, and nullptr.
  2. Learning enum class: Replace ordinary enums with enum classes, and see which implicit conversions are forbidden.
  3. When learning smart pointers, first write a manual return example of new/delete, then convert it to unique_ptr.
  4. 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.

音乐页