Excerpts and key points from various C++ learning materials.

”Luo Jianfeng’s C++ Practical Notes”

Build Tools

Common C++ build tools: CMake, SCons. At this stage, C++11 is a must-know.

C++ Programming Paradigms

What is a programming paradigm? While there’s no single authoritative definition, a common explanation is: a “paradigm” is a methodology — a set of ideas, rules, habits, patterns, and idioms that guide how you write code. C++ is a multi-paradigm language. Specifically, modern C++ (11/14 onward) supports five major paradigms:

  • Procedural is the most fundamental paradigm in C++. Its core idea is “commands” — typically sequential statements and subroutines (functions) that break a task into steps to achieve a goal.
  • Object-Oriented is another basic paradigm. Its core ideas are “abstraction” and “encapsulation,” decomposing tasks into high-cohesion, low-coupling objects that communicate and collaborate. It emphasizes relationships and interfaces rather than step-by-step procedures.
  • Generic Programming gained popularity after STL (Standard Template Library) was incorporated into the C++ standard. Its core idea is “everything is a type” — parameterized types and type erasure — using templates rather than inheritance for code reuse, resulting in higher runtime efficiency and cleaner code.
  • Template Metaprogramming has “type computation” at its core, operating on compile-time-visible types. Code is executed by the compiler, not the CPU at runtime.
  • Functional — here “functional” does not mean subroutines written as C++ functions, but mathematical, side-effect-free functions. The core idea is “everything is callable,” processing data through a series of continuous or nested function calls.

For user-facing applications, studying “generic” and “functional” covers about 90% of development needs. For library development targeting other programmers, you’ll need to dive deeper into “generic” and “template meta” to optimize interfaces and runtime performance.

”Modern C++ in Practice: 30 Lessons”

Move Semantics and Reference Collapsing

Per C++ rules, if you provide a move constructor but no copy constructor, the latter is automatically disabled.

Regarding temporary object (prvalue) lifetime: a temporary object is destroyed in reverse order of construction once the full expression containing it has been evaluated — unless lifetime extension applies. C++ has a special lifetime extension rule for temporaries: if a temporary (prvalue) is bound to a reference, its lifetime is extended to match that of the reference variable.

For a concrete type T, its lvalue reference is T& and its rvalue reference is T&&. The questions are:

  1. Does T& always mean an lvalue reference?
  2. Does T&& always mean an rvalue reference?

The answer to the first is “yes”; the answer to the second is “no.”

The key lies in template code, where type parameter deduction can yield reference types. The essential points are:

  • For code like template <typename T> foo(T&&), if the passed argument is an lvalue, T is deduced as an lvalue reference; if the argument is an rvalue, T is deduced as the type itself.
  • If T is an lvalue reference, then T&& still collapses to an lvalue reference — i.e., type& && collapses to type&.
  • If T is a concrete type, then T&& is naturally an rvalue reference.

Perfect Forwarding

Many standard library functions don’t even know the target parameter types, yet they must preserve the value category: lvalues remain lvalues, rvalues remain rvalues. This functionality is provided in the C++ standard library as std::forward. Like std::move, it relies on the reference collapsing mechanism.

#include<iostream>
using namespace std;

#define FORWARD

class shape {
public:
    shape(){}
    virtual ~shape(){}
};

class circle: public shape {
};


void foo(const shape&) {
    puts("foo(const shape&)");
}

void foo(shape&&) {
    puts("foo(shape&&)");
}

# ifndef FORWARD
void bar(const shape& s) {
    puts("bar(const shape&)");
    foo(s);
}

void bar(shape&& s) {
    puts("bar(shape&&)");  
    foo(s);
}
#else 
template<class T>
void bar(T&& s) {
    foo(std::forward<T>(s));    // perfect forwarding: even though s is an rvalue, it's forwarded as an rvalue to foo, calling foo(shape&&)
}
#endif

int main() {
    bar(circle());

    return 0;
}