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:
- Does
T&always mean an lvalue reference? - 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,Tis deduced as an lvalue reference; if the argument is an rvalue,Tis deduced as the type itself. - If
Tis an lvalue reference, thenT&&still collapses to an lvalue reference — i.e.,type& &&collapses totype&. - If
Tis a concrete type, thenT&&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;
}