简介
C++11 引入的 右值引用(rvalue reference) 和 移动语义(move semantics) 是现代 C++ 的核心特性之一,它们的主要目的是 提升程序性能,避免不必要的拷贝开销,尤其是在涉及大对象或资源管理时。
一、左值与右值基础
在讲右值引用之前,先来区分左值(lvalue)和右值(rvalue):
| 类型 | 描述 |
|---|---|
| 左值(lvalue) | 有名字的、可取地址的对象,生命周期超过表达式。比如变量 int a = 10; 中的 a。 |
| 右值(rvalue) | 临时对象、字面量等,不可取地址,生命周期较短。比如 10、a + b、std::string("temp")。 |
示例:
1int a = 10;
2int b = a; // a 是左值
3int c = a + 1; // a + 1 是右值
二、右值引用(rvalue reference)
语法:
1T&& var;
这里 && 是右值引用。它只能绑定到右值。
示例:
1int a = 10;
2int&& r = 20; // OK,20 是右值
3int&& s = a; // 错误,a 是左值,不能绑定到右值引用
特点:
可以“窃取”右值的资源(而不是深拷贝),从而提高性能。
引出移动构造函数和移动赋值运算符。
三、移动语义(Move Semantics)
问题背景:
在 C++03 中,如果你返回一个大型对象,往往涉及昂贵的拷贝开销:
1std::vector<int> makeVec() {
2 std::vector<int> v(1000, 42); // 初始化 1000 个元素
3 return v; // C++03 会调用拷贝构造函数
4}
C++11 中引入 移动构造函数 和 移动赋值运算符 来解决这个问题。
四、移动构造函数 & 移动赋值运算符
定义:
1class MyClass {
2public:
3 MyClass(MyClass&& other); // 移动构造函数
4 MyClass& operator=(MyClass&& other); // 移动赋值运算符
5};
特点:
参数类型是
T&&。实现中窃取资源,并让源对象处于一个“可析构”的空状态。
示例:
1class MyString {
2private:
3 char* data;
4public:
5 MyString(const char* str) {
6 data = new char[strlen(str)+1];
7 strcpy(data, str);
8 }
9
10 // 移动构造函数
11 MyString(MyString&& other) noexcept {
12 data = other.data;
13 other.data = nullptr;
14 }
15
16 ~MyString() {
17 delete[] data;
18 }
19};
这样,在返回临时 MyString 时,就不会再调用拷贝构造,而是直接移动其内部指针。
五、std::move 与移动语义激活
什么是 std::move?
它并不移动,而是一个 类型转换工具,将左值强制转换为右值引用,从而激活移动语义。
示例:
1MyString a = "Hello";
2MyString b = std::move(a); // a 变为右值,调用移动构造
此时 b 接管了 a 的内部资源,而 a 成为空状态。
六、标准容器中的应用
C++11 STL 容器(如 std::vector、std::map)都支持移动语义。
示例:
1std::vector<std::string> v;
2std::string s = "hello";
3v.push_back(std::move(s)); // 避免拷贝,提高效率
七、Rule of Five(五法则)
在 C++11 中,如果你的类涉及资源管理(如裸指针),你可能需要实现以下五个函数:
析构函数
拷贝构造函数
拷贝赋值运算符
移动构造函数
移动赋值运算符
否则资源转移或释放可能会出错。
八、完美转发与泛型代码
在模板中,右值引用还可以配合 std::forward 实现 完美转发,但这里涉及引用折叠和万能引用(T&&)的内容,这是右值引用的进阶应用。
九、总结
| 概念 | 描述 |
|---|---|
| 右值引用 T&& | 一种只能绑定到右值的引用,用于移动语义 |
| 移动构造函数 | 从临时对象“窃取资源”的构造函数,避免拷贝 |
std::move | 将左值转换为右值引用,触发移动语义 |
| 性能提升点 | 尤其适用于大对象、动态分配资源类、容器操作等场景 |
例程
接下来我们手写一个类 MyString,它内部维护一个动态分配的 C 字符串,我们将:
✅ 实现构造函数、析构函数
✅ 实现拷贝构造、拷贝赋值(传统语义)
✅ 实现移动构造、移动赋值(现代语义)
✅ 编写测试用例对比两者性能和行为
🔧 第一步:MyString 类的基础结构(构造 / 析构)
1#include <iostream>
2#include <cstring>
3
4class MyString {
5private:
6 char* data;
7
8public:
9 // 默认构造
10 MyString() : data(nullptr) {}
11
12 // 构造函数(从 const char* 初始化)
13 MyString(const char* str) {
14 std::cout << "[Ctor] Constructing from C-string\n";
15 data = new char[strlen(str) + 1];
16 strcpy(data, str);
17 }
18
19 // 析构函数
20 ~MyString() {
21 std::cout << "[Dtor] Destructing\n";
22 delete[] data;
23 }
24
25 void print() const {
26 std::cout << "Content: " << (data ? data : "null") << "\n";
27 }
28};
📋 第二步:添加拷贝构造 & 拷贝赋值
1 // 拷贝构造函数
2 MyString(const MyString& other) {
3 std::cout << "[CopyCtor] Deep copy\n";
4 if (other.data) {
5 data = new char[strlen(other.data) + 1];
6 strcpy(data, other.data);
7 } else {
8 data = nullptr;
9 }
10 }
11
12 // 拷贝赋值运算符
13 MyString& operator=(const MyString& other) {
14 std::cout << "[CopyAssign] Deep copy assign\n";
15 if (this != &other) {
16 delete[] data;
17 if (other.data) {
18 data = new char[strlen(other.data) + 1];
19 strcpy(data, other.data);
20 } else {
21 data = nullptr;
22 }
23 }
24 return *this;
25 }
🚀 第三步:添加移动构造 & 移动赋值
1 // 移动构造函数
2 MyString(MyString&& other) noexcept {
3 std::cout << "[MoveCtor] Moving\n";
4 data = other.data;
5 other.data = nullptr;
6 }
7
8 // 移动赋值运算符
9 MyString& operator=(MyString&& other) noexcept {
10 std::cout << "[MoveAssign] Move assign\n";
11 if (this != &other) {
12 delete[] data;
13 data = other.data;
14 other.data = nullptr;
15 }
16 return *this;
17 }
✅ 完整测试代码
1int main() {
2 std::cout << "=== Creating a ===\n";
3 MyString a("Hello");
4
5 std::cout << "\n=== Copying to b ===\n";
6 MyString b = a; // Copy ctor
7
8 std::cout << "\n=== Moving to c ===\n";
9 MyString c = std::move(a); // Move ctor
10
11 std::cout << "\n=== Assign copy to d ===\n";
12 MyString d;
13 d = b; // Copy assignment
14
15 std::cout << "\n=== Assign move to e ===\n";
16 MyString e;
17 e = std::move(c); // Move assignment
18
19 std::cout << "\n=== Final states ===\n";
20 std::cout << "a: "; a.print(); // Should be null
21 std::cout << "b: "; b.print();
22 std::cout << "c: "; c.print(); // Should be null
23 std::cout << "d: "; d.print();
24 std::cout << "e: "; e.print();
25}
🧠 观察输出(关键点)
=== Creating a ===
[Ctor] Constructing from C-string
=== Copying to b ===
[CopyCtor] Deep copy
=== Moving to c ===
[MoveCtor] Moving
=== Assign copy to d ===
[CopyAssign] Deep copy assign
=== Assign move to e ===
[MoveAssign] Move assign
拷贝构造/赋值是 深拷贝:申请新内存 + 拷贝内容。
移动构造/赋值是 浅转移:直接转移指针,不复制内容。
🔍 小结
| 操作 | 开销 | 使用场景 |
|---|---|---|
| 拷贝构造/赋值 | 分配内存 + 拷贝内容 | 保证原对象不变 |
| 移动构造/赋值 | 只转移指针 | 临时对象 / 不再需要原对象时 |
🔥 Bonus:性能对比建议
可以用如下方式模拟大量对象,比较是否启用了移动:
1std::vector<MyString> vec;
2vec.push_back(MyString("Temporary")); // 会调用移动构造
或者加上 -fno-elide-constructors 来关闭编译器优化(比如 RVO)观察真实行为。
进阶
下面我们深入讲解 C++11 引入的两个非常重要的进阶特性:
🌟 一、完美转发(Perfect Forwarding)& 万能引用(Universal Reference)
它们是右值引用的重要延伸,主要用于泛型编程中,尤其是在模板中构造或转发参数。
🧩 二、问题背景:普通函数模板中的值传递会丢失“值类别”
来看一个例子:
1void process(int& x) { std::cout << "LValue\n"; }
2void process(int&& x) { std::cout << "RValue\n"; }
3
4template<typename T>
5void wrapper(T x) {
6 process(x); // 始终是 LValue!
7}
测试:
1int a = 10;
2wrapper(a); // 输出 LValue ✅
3wrapper(10); // 输出 LValue ❌(期望 RValue)
❗ 问题:模板参数 T 是值传递,x 总是一个左值,哪怕你传的是右值。
✅ 三、万能引用(Universal Reference)
1template<typename T>
2void wrapper(T&& x); // 这个 T&& 就是 **万能引用**
🔥 特点:
它既能接受 左值,也能接受 右值。
万能引用 ≠ 普通右值引用。
是否是万能引用,取决于:
形式是
T&&(必须是模板参数+&&)T是模板类型参数(推导发生)
示例:
1template<typename T>
2void wrapper(T&& x) {
3 process(std::forward<T>(x)); // 完美转发
4}
💡 四、std::forward 实现完美转发
在模板中,T&& 是万能引用,要保留原始“值类别”(左值或右值),就需要 std::forward<T>(x)。
转发规则:
1std::forward<T>(x)
等价于:
如果
T是左值引用类型(T=int&),结果是int&(左值)如果
T是非引用(T=int),结果是int&&(右值)
🎯 五、完美转发定义与作用
完美转发(Perfect Forwarding):
是指在模板中将参数原封不动地“转发”给另一个函数,包括:
是否为左/右值
是否为 const
引用修饰
典型使用场景:
构造工厂(如
make_shared)容器内部
emplace_back任意包装器函数(如日志、计时器等)
🛠 六、例子:完美构造工厂
1class Widget {
2public:
3 Widget(int, double) { std::cout << "Construct Widget\n"; }
4};
5
6template<typename T, typename... Args>
7T* factory(Args&&... args) {
8 return new T(std::forward<Args>(args)...); // 保留参数原本形态
9}
使用:
1auto w = factory<Widget>(42, 3.14); // 完美转发参数到 Widget 构造函数
🧠 七、万能引用 vs 普通右值引用
| 语法 | 是否万能引用 | 接受左值 | 接受右值 |
|---|---|---|---|
T&& in 模板 | ✅ 是 | ✅ 是 | ✅ 是 |
int&& | ❌ 否 | ❌ 否 | ✅ 是 |
const T&& | ❌ 否 | ❌ 否 | ✅ 是 |
🧪 八、验证万能引用行为的小测试
1template<typename T>
2void test(T&& x) {
3 if constexpr (std::is_lvalue_reference<T>::value)
4 std::cout << "T is lvalue ref\n";
5 else
6 std::cout << "T is not lvalue ref\n";
7}
8
9int a = 10;
10test(a); // T = int& → T is lvalue ref
11test(10); // T = int → T is not lvalue ref
📌 九、总结对比
| 概念 | 用途 | 特点 |
|---|---|---|
右值引用 T&& | 类中移动构造、移动赋值 | 只能绑定右值 |
万能引用 T&& | 模板参数转发 | 能接左/右值 |
std::forward | 保留原值类别实现完美转发 | 必须搭配万能引用使用 |
🎁 Bonus:std::move vs std::forward
| 工具 | 作用 | 使用场景 |
|---|---|---|
std::move | 将表达式转为右值引用 | 激活移动语义 |
std::forward | 根据模板参数 T 判断值类别 | 实现完美转发 |
进阶例程
下面我们将分三步来深入理解并实践:
🔍 一、完美转发在 STL 中的实际应用
🎯 1. std::make_shared<T>(...) 如何用完美转发构造对象?
目标:
避免重复写
new。传什么参数都能构造
T,保持值类别(左值 / 右值)。
简化实现:
1template<typename T, typename... Args>
2std::shared_ptr<T> my_make_shared(Args&&... args) {
3 return std::shared_ptr<T>(new T(std::forward<Args>(args)...));
4}
Args&&...是万能引用包。std::forward<Args>(args)...完美转发参数到T的构造函数。
🎯 2. emplace_back 如何避免多次构造?
1std::vector<std::string> vec;
2vec.emplace_back("hello"); // 构造一次 ✅
3vec.push_back("hello"); // 构造 + 拷贝或移动 ❌
emplace_back 内部:
1template<typename... Args>
2void emplace_back(Args&&... args) {
3 // 用转发构造元素到末尾
4 new (&storage[last_index]) T(std::forward<Args>(args)...);
5}
避免临时对象,直接构造在容器内存中。
完美转发使它接受各种形式的构造参数(左/右值、引用、常量等)。
✨ 二、手写 make_unique 风格模板,支持完美转发
✅ 实现目标:
1template<typename T, typename... Args>
2std::unique_ptr<T> my_make_unique(Args&&... args);
✅ 实现代码:
1#include <memory>
2#include <utility>
3
4template<typename T, typename... Args>
5std::unique_ptr<T> my_make_unique(Args&&... args) {
6 return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
7}
✅ 测试类和测试代码:
1#include <iostream>
2
3class MyClass {
4public:
5 MyClass(int x, const std::string& s) {
6 std::cout << "Constructing MyClass with x=" << x << ", s=" << s << "\n";
7 }
8};
9
10int main() {
11 auto ptr = my_make_unique<MyClass>(42, std::string("hello"));
12 return 0;
13}
输出:
Constructing MyClass with x=42, s=hello
💡 三、你应该记住的完美转发套路
1template<typename T, typename... Args>
2T* factory(Args&&... args) {
3 return new T(std::forward<Args>(args)...); // 保留原始形态
4}
模板 + 万能引用 +
std::forward是完美转发的三件套。
🔄 四、std::move vs std::forward:区别总结
| 工具 | 适用参数 | 值类别 | 用途 |
|---|---|---|---|
std::move(x) | 任意表达式 | 强制右值 | 激活移动语义 |
std::forward<T>(x) | 模板参数 x | 保留原值类别 | 完美转发(模板中) |
🧪 五、挑战题(练习建议)
你可以自己尝试:
实现一个
my_emplace_back函数(模拟vector::emplace_back)实现一个通用工厂函数
construct_with_logging<T>(Args&&...),转发构造并记录日志
面试题
当然可以!下面是一些涵盖 右值引用、移动语义、完美转发与万能引用 的 C++11 面试题,按照从易到难的顺序整理,并配有标准答案与讲解,适合用来检验理解或准备面试。
🌱 一、基础题:右值引用 & 移动语义
✅ 题目 1:以下哪些可以绑定到 int&&?
1int a = 10;
2int& lref = a;
3int&& rref = 20;
问:int&& r = a; 和 int&& r2 = lref; 是否合法?
✅ 答案:
int&& r = a;❌ 不合法,a是左值,不能绑定到右值引用。int&& r2 = lref;❌ 同样不合法,lref是左值引用。
✅ 讲解:
右值引用 (T&&) 只能绑定到右值(匿名变量、临时对象、字面值),不能绑定到左值或左值引用。
✅ 题目 2:以下语句中,是否发生了移动语义?
1std::string a = "hello";
2std::string b = std::move(a);
✅ 答案:
✅ 是,发生了移动构造。
✅ 讲解:
std::move(a)将a转换为右值引用(即std::string&&)然后调用
std::string的移动构造函数。
🌿 二、进阶题:完美转发 & 万能引用
✅ 题目 3:下面哪个是万能引用?
1template<typename T>
2void func(T&& arg);
3
4void func2(int&& arg);
✅ 答案:
func(T&& arg)✅ 是万能引用。func2(int&& arg)❌ 不是,是纯右值引用。
✅ 讲解:
万能引用的语法必须是 T&&,并且 T 是模板类型参数(推导发生)。int&& 是固定类型,不具备万能性。
✅ 题目 4:下面的 call() 是否完美转发了参数?
1void target(int& x) { std::cout << "lvalue\n"; }
2void target(int&& x) { std::cout << "rvalue\n"; }
3
4template<typename T>
5void call(T&& arg) {
6 target(std::forward<T>(arg));
7}
调用:
1int a = 5;
2call(a); // ?
3call(10); // ?
✅ 答案:
call(a);打印lvaluecall(10);打印rvalue
✅ 讲解:
T= int&时,forward<T>(arg)→ 左值T= int时,forward<T>(arg)→ 右值
完美转发使得 arg 的值类别得以保持。
🌳 三、高阶题:手写构造与转发逻辑
✅ 题目 5:解释 std::forward<T>(arg) 的实现原理。
✅ 答案(简化版):
1template<typename T>
2T&& forward(typename std::remove_reference<T>::type& arg) {
3 return static_cast<T&&>(arg);
4}
✅ 讲解:
remove_reference<T>::type取得基础类型(去掉 & 和 &&)使用
static_cast<T&&>保留 T 的值类别(左值或右值)
✅ 题目 6:用完美转发实现一个 make_unique<T> 函数模板。
✅ 答案:
1template<typename T, typename... Args>
2std::unique_ptr<T> my_make_unique(Args&&... args) {
3 return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
4}
✅ 讲解:
Args&&...是万能引用包forward<Args>(args)...保留所有参数的值类别支持构造任何参数组合的对象
🌲 四、实践与陷阱题
✅ 题目 7:下面这个 wrapper 有什么 bug?
1template<typename T>
2void wrapper(T&& arg) {
3 process(arg);
4}
✅ 答案:
❌ 错误:这里会将 arg 作为左值传递,即使是右值也会变成左值。
✅ 正确写法:
1process(std::forward<T>(arg));
✅ 讲解:
所有变量都有名字 → 是左值
要保留右值性质,必须
forward
🎯 总结:面试官常问的核心点
| 问题类型 | 考察点 |
|---|---|
| 右值引用能否绑定左值? | 区分左值 / 右值引用 |
| std::move 和 std::forward 区别 | 用途不同:强制右值 vs 保留值类别 |
| 万能引用和右值引用如何区分? | 模板中 T&& 是万能引用 |
| 如何实现 make_shared / unique | 完美转发 + 模板 |
| emplace_back vs push_back | 是否构造临时对象 + 完美转发 |
STL相关面试题
下面是一些STL 实践层面的完美转发题目,涵盖了 std::vector::emplace_back、容器构造、std::function 转发、std::bind 陷阱等高级用法,非常贴近实战和面试常考点。
🌟 STL 级完美转发实践题目(含答案)
✅ 题目 1:emplace_back 与 push_back 的区别及适用场景?
代码:
1std::vector<std::string> v;
2std::string s = "hello";
3
4v.push_back(s); // ①
5v.push_back(std::move(s)); // ②
6v.emplace_back("world"); // ③
问题:
分析每行代码是否涉及复制或移动构造?
哪一行使用了完美转发?
✅ 答案:
①:复制构造(传的是左值)
②:移动构造(右值引用)
③:构造就地(
emplace_back),避免临时对象,调用构造函数直接构造"world"字符串
第 ③ 行使用了完美转发:
emplace_back("world")会调用std::string的构造函数,传入转发参数。
✅ 题目 2:以下 emplace_back 和 push_back 哪个更高效?为什么?
1std::vector<std::pair<int, std::string>> v;
2v.push_back(std::make_pair(1, "hello"));
3v.emplace_back(1, "hello");
✅ 答案:
emplace_back(1, "hello")更高效。原因:避免了中间临时
pair<int, string>对象的创建和移动/复制构造。它直接使用完美转发调用
pair的构造函数。
✅ 题目 3:实现一个 log_and_forward 函数,它接收任意参数并调用目标函数,同时打印参数个数。
1template<typename Func, typename... Args>
2void log_and_forward(Func&& func, Args&&... args) {
3 // 实现:打印参数个数,然后调用 func(args...)
4}
✅ 答案:
1#include <iostream>
2#include <utility>
3
4template<typename Func, typename... Args>
5void log_and_forward(Func&& func, Args&&... args) {
6 std::cout << "Calling with " << sizeof...(Args) << " arguments\n";
7 std::forward<Func>(func)(std::forward<Args>(args)...);
8}
✅ 测试用例:
1void print(int x, const std::string& s) {
2 std::cout << "x=" << x << ", s=" << s << "\n";
3}
4
5int main() {
6 log_and_forward(print, 42, "hello");
7}
✅ 题目 4:如何实现一个类模板 Wrapper<T>,它可以完美转发构造参数到 T 的构造函数?
目标:
1Wrapper<MyClass> w(args...); // 直接构造 MyClass,支持任意构造参数
✅ 答案:
1template<typename T>
2class Wrapper {
3public:
4 template<typename... Args>
5 Wrapper(Args&&... args)
6 : instance_(std::forward<Args>(args)...) {}
7
8 T& get() { return instance_; }
9
10private:
11 T instance_;
12};
✅ 题目 5:使用 std::function 时,是否能实现完美转发?试解释为何以下写法不具备转发能力:
1std::function<void(int&)> f = [](int& x) { std::cout << x << "\n"; };
2int a = 5;
3f(a); // OK
4f(5); // ❌ 编译错误
✅ 答案:
std::function<void(int&)>只能接受左值引用。f(5)是右值,不能绑定到int&,因此出错。std::function是类型擦除机制,丢失了泛型与完美转发能力。若想完美转发,应该使用模板函数或
std::invoke。
✅ 题目 6(高难):实现一个 defer 函数(类似 Go),延迟执行传入的 lambda,在作用域结束时自动触发。
1auto d = defer([]() { std::cout << "deferred\n"; });
✅ 答案:
1template<typename F>
2class Defer {
3 F func_;
4public:
5 explicit Defer(F&& f) : func_(std::forward<F>(f)) {}
6 ~Defer() { func_(); }
7};
8
9template<typename F>
10Defer<F> defer(F&& f) {
11 return Defer<F>(std::forward<F>(f));
12}
✅ 应用示例:
1int main() {
2 auto _ = defer([]() { std::cout << "defer\n"; });
3 std::cout << "in scope\n";
4}
5// 输出:
6// in scope
7// defer
📘 Bonus:面试官可能问的深入问题
| 问题 | 涉及点 |
|---|---|
为什么 emplace_back 比 push_back 快? | 少一次构造/移动 |
std::forward 和 std::move 区别? | 保留 vs 强制右值 |
为何 std::function 无法完美转发? | 类型擦除,不保留模板推导 |
| 万能引用的条件? | T&& 且模板参数发生推导 |
深入问题
下面是这四个面试高频问题的标准答案和详细讲解,每个都展开深入解析,帮助你从机制层面真正理解这些 STL 与语法特性的原理。
✅ 问题 1:为什么 emplace_back 比 push_back 快?
✅ 答案简述:
emplace_back 避免了临时对象构造或复制/移动,因此通常比 push_back 更高效。
🔍 详细解释:
push_back(obj)接受一个已经构造好的对象,它会进行一次拷贝或移动构造。1v.push_back(std::string("hello")); // 构造 -> 移动emplace_back(args...)接受任意构造参数,在容器内就地构造对象,避免了临时对象的创建。1v.emplace_back("hello"); // 直接构造,无移动或复制
🧠 内部机制:
1template<class... Args>
2void emplace_back(Args&&... args) {
3 // 完美转发到对象构造函数
4 construct_at(end(), std::forward<Args>(args)...);
5}
✅ 问题 2:std::forward 和 std::move 的区别?
✅ 答案简述:
std::move: 强制将对象转为右值。std::forward: 根据模板类型T保留原始值类别(左值/右值)。
🔍 详细解释:
| 特性 | std::move | std::forward<T> |
|---|---|---|
| 目的 | 强制转为右值 | 保留原始值类别 |
| 类型推导 | 不依赖模板 | 依赖模板类型 T |
| 使用场景 | 实现移动语义 | 实现完美转发 |
| 会破坏原对象? | 可能 | 取决于 T 推导结果 |
✅ 示例对比:
1template<typename T>
2void func(T&& arg) {
3 std::move(arg); // 始终右值(破坏性)
4 std::forward<T>(arg); // 如果 arg 是左值,仍保持左值
5}
✅ 问题 3:为何 std::function 无法完美转发?
✅ 答案简述:
std::function 是类型擦除容器,不能保留参数类型和引用类别信息,因此无法实现完美转发。
🔍 详细解释:
std::function是用于存储任意可调用对象的通用容器,类型签名固定,例如:1std::function<void(int&)> f;这里参数类型一旦设定为
int&,调用时只能传左值,无法接受右值(如字面值)。
🧠 原因核心:
完美转发依赖模板推导、参数类别推导(T&&),
std::function丢失了这些信息。它内部的调用语义不区分参数的左值/右值引用类别。
✅ 示例说明:
1auto lambda = [](auto&& arg) {
2 std::cout << typeid(arg).name() << "\n";
3};
4
5std::function<void(int&&)> f = lambda; // ❌ 编译失败:类型不匹配
✅ 问题 4:万能引用的条件?
✅ 答案简述:
- 万能引用是
T&&的形式,并且T是模板参数,必须发生类型推导。
🔍 判定万能引用的两个关键条件:
函数参数必须是
T&&的形式(模板类型 +&&)必须通过 类型推导 得到
T(非显式指定)
✅ 正例(是万能引用):
1template<typename T>
2void func(T&& arg); // ✅ 满足万能引用条件
func(5)时,T 为int→int&&int a = 10; func(a);→ T 为int&,参数实际类型为int& &&→ 折叠为int&
❌ 反例(不是万能引用):
1void f(int&&); // 右值引用,非模板
2template<typename T>
3void f2(std::vector<T>&&); // 固定类型右值引用,非万能引用
🧠 推导 + 引用折叠:
| T | T&& | 结果 |
|---|---|---|
| int | int&& | int&& |
| int& | int& && | int& |
引用折叠是万能引用的核心基础之一。
✅ 总结表:
| 问题 | 关键点 |
|---|---|
为什么 emplace_back 快? | 避免中间对象构造,直接就地构造 |
std::forward vs std::move | 保留原值类别 vs 强制右值 |
std::function 不支持转发? | 类型擦除,丢失推导与引用类别信息 |
| 万能引用条件? | T&& 且 T 被推导,支持引用折叠机制 |
STL 层级完美转发相关的 C++11 编程练习题
下面我为你详细解答这 5 道 STL 层级完美转发相关的 C++11 编程练习题,帮助你从实际代码中掌握完美转发、万能引用、右值引用和类型推导的精髓。
✅ 题 1:log_and_forward — 记录参数个数并完美转发
✏️ 要求:
接收任意可调用对象
f和任意参数args...打印参数个数
调用
f(args...),保持值类别(完美转发)
✅ 实现:
1#include <iostream>
2#include <utility>
3
4template<typename F, typename... Args>
5decltype(auto) log_and_forward(F&& f, Args&&... args) {
6 std::cout << "Argument count: " << sizeof...(Args) << "\n";
7 return std::forward<F>(f)(std::forward<Args>(args)...);
8}
🧪 示例:
1void print(int x, const std::string& msg) {
2 std::cout << msg << ": " << x << "\n";
3}
4
5int main() {
6 log_and_forward(print, 42, "value"); // 输出 Argument count: 2,value: 42
7}
✅ 题 2:Wrapper<T> 类模板,构造时完美转发参数
✏️ 要求:
类模板
Wrapper<T>内部持有一个
T t_构造函数使用完美转发构造
T
✅ 实现:
1#include <utility>
2
3template<typename T>
4class Wrapper {
5public:
6 template<typename... Args>
7 Wrapper(Args&&... args)
8 : t_(std::forward<Args>(args)...) {}
9
10 T& get() { return t_; }
11
12private:
13 T t_;
14};
🧪 示例:
1#include <string>
2#include <iostream>
3
4int main() {
5 Wrapper<std::string> w("hello");
6 std::cout << w.get() << "\n"; // 输出 hello
7}
✅ 题 3:模拟 Go 的 defer(作用域自动调用)
✏️ 要求:
类似 Go 的
defer接收一个 lambda,作用域结束自动调用它
✅ 实现(RAII 方案):
1#include <functional>
2
3class Defer {
4public:
5 Defer(std::function<void()> fn) : fn_(std::move(fn)) {}
6 ~Defer() { fn_(); }
7
8private:
9 std::function<void()> fn_;
10};
11
12#define CONCAT_IMPL(x, y) x##y
13#define CONCAT(x, y) CONCAT_IMPL(x, y)
14#define defer(code) Defer CONCAT(_defer_, __LINE__)([&](){ code; })
🧪 示例:
1#include <iostream>
2
3int main() {
4 defer(std::cout << "Exiting scope!\n";);
5 std::cout << "Inside scope\n";
6 // Output:
7 // Inside scope
8 // Exiting scope!
9}
✅ 题 4:is_universal_reference 判断是否为万能引用
✏️ 思路:
万能引用只有在模板中
T&&并发生类型推导才成立用
std::is_rvalue_reference<T&&>和std::is_lvalue_reference<T>配合判断
✅ 实现:
1#include <iostream>
2#include <type_traits>
3
4template<typename T>
5void is_universal_reference(T&& arg) {
6 if constexpr (std::is_lvalue_reference<T>::value)
7 std::cout << "arg is bound to LVALUE: T = " << typeid(T).name() << "\n";
8 else
9 std::cout << "arg is bound to RVALUE: T = " << typeid(T).name() << "\n";
10}
🧪 示例:
1int main() {
2 int x = 5;
3 is_universal_reference(x); // LVALUE
4 is_universal_reference(10); // RVALUE
5}
✅ 题 5:分析以下声明是否为万能引用
1template<typename T> void f1(T&&); // ✅ 是万能引用
2void f2(int&&); // ❌ 不是万能引用,只能绑定右值
3template<typename T> void f3(std::vector<T>&&); // ❌ 不是万能引用,是右值引用模板
🧠 理由:
| 函数声明 | 是否万能引用 | 原因说明 |
|---|---|---|
template<typename T> void f1(T&&) | ✅ 是 | T 是模板参数,发生类型推导,引发引用折叠 |
void f2(int&&) | ❌ 否 | 不是模板,无类型推导,是普通右值引用 |
template<typename T> void f3(std::vector<T>&&) | ❌ 否 | std::vector<T>&& 是具体类型,不能引用折叠 |
✅ 最后提示:如何判断是不是万能引用?
只要写法是
T&&并且T是模板参数,并且 T 是被推导出来的,就可以判断为万能引用。
最后修改于 2025-05-15 14:45