👋 欢迎阅读

🔥愿旖旎 · 个人主页
📘学习专栏: 《算法专栏》《LangChain学习》《贪心算法》
🌄 钱塘江上潮信来,今日方知我是我
✨当前学习内容: 《C++学习》
📑 目录
一、前置讲解
在进入正文前,先花 10 秒了解本篇会反复用到的核心概念,带着印象去读正文,学习效率更高。
🧩 前置知识一:左值引用的短板
做参数、做返回值都能省拷贝 ,但当返回的是函数内局部对象 时,只能传值返回,于是又产生拷贝。
🚚 前置知识二:移动构造 / 移动赋值
把源对象(右值)的资源直接窃取过来 ,不做深拷贝,代价远小于拷贝。
🔁 前置知识三:std::move
它不搬任何东西 ,只是把左值强制转换成右值引用,从而让移动构造能接上。
📮 前置知识四:万能引用 T&&
模板里的 T&& 不是右值引用 ,左右值都能收------但具名变量本身是左值。
➡️ 前置知识五:std::forward
完美转发 :把实参的原生类型和值类别原样传下去,左值还是左值、右值还是右值。
二、右值引用的使用场景和意义
前面我们看到左值引用既可以引用左值、又可以引用右值 ,那为什么 C++11 还要提出右值引用?是不是画蛇添足?------先看左值引用的短板,再看右值引用如何补齐。
2.1 左值引用的使用场景
左值引用做参数和做返回值都可以提高效率。
cpp
void func1(bit::string s) // 传值:拷贝一份,代价大
{}
void func2(const bit::string& s) // 传引用:不拷贝
{}
int main()
{
bit::string s1("hello world");
// func1 和 func2 的调用对比,可以看出左值引用做参数能减少拷贝
func1(s1);
func2(s1);
}
2.2 左值引用的短板
但是当函数返回的对象是一个局部变量 时,它出了函数作用域就不存在了 ,就不能使用左值引用返回,只能传值返回。
例如 bit::string to_string(int value) 函数,这里只能使用传值返回 ,而传值返回会导致至少 1 次拷贝构造(旧一点的编译器可能是两次拷贝构造)。


在函数中传值返回是返回一个临时对象 ,如果该临时对象符合将亡值的概念 ,那么就可以采用移动构造 而不采取拷贝构造(前提是有移动构造)。
右值引用和移动语义解决的正是上述问题:
在 bit::string 中增加移动构造 ,移动构造的本质是将参数右值的资源窃取过来、占为己有 ,那么就不用深拷贝 了------所以它叫做移动构造 ,就是窃取别人的资源来构造自己。

不仅仅有移动构造,还有移动赋值:
在 bit::string 类中增加移动赋值 函数,再去调用 bit::to_string(1234)------不过这次是将 bit::to_string(1234) 返回的右值对象赋值给 ret1 对象 ,这时调用的是移动赋值。
cpp
// 普通赋值:左值引用,需要生成对象
string operator=(const string& s)
{
if (this != &s) // 自赋值检测
{
string tmp(s); // 先拷贝一份
swap(tmp); // 交换后 *this 拿到新内容
}
return *this; // 按值返回:返回 *this 的一份副本
}
// 移动赋值:右值引用,直接窃取资源
string operator=(string&& s)
{
if (this != &s)
{
swap(s); // 窃取 s 的资源
}
return *this; // 按值返回
}
运行结果(关键输出):
string& operator=(string&& s) -- 移动语义
| 对比项 | 拷贝构造 / 拷贝赋值 | 移动构造 / 移动赋值 |
|---|---|---|
| 参数 | const T&(常量左值引用) |
T&&(右值引用) |
| 做的事 | 重新分配资源 + 深拷贝内容 | 窃取源对象资源(换指针) |
| 源对象 | 保持不变 | 被置空/失效(资源已交出去) |
因此右值引用解决的场景:
| 场景 | 说明 |
|---|---|
| 需要深拷贝、且只能用传值返回/值传递的类 | 即不能用左值引用的情况,用移动构造/移动赋值省掉深拷贝 |
| 独占资源的类 | 该类对象不能被拷贝、只能被转移,也只能靠右值引用 |
2.3 右值引用引用左值:std::move
按照语法,右值引用只能引用右值 ------但右值引用一定不能引用左值吗?有些场景下,可能真的需要用右值去引用左值以实现移动语义 。当需要用右值引用引用一个左值时,可以通过 move 函数将左值转化为右值。
C++11 中,std::move() 函数位于 <utility> 头文件中 。这个函数名字具有迷惑性 :它并不搬移任何东西,唯一的功能就是将一个左值强制转化为右值引用,然后实现移动语义。
move 的理解:
move不会改变左值对象本身,而是返回一个"把该左值转换成右值引用"的结果 (本质是static_cast<T&&>)。
验证 move 到底做了什么:
cpp
int main()
{
int a = 10;
// std::move 只是把 a 转成右值引用,a 本身没有任何变化
int&& r = std::move(a);
cout << a << endl; // 10:a 的值没变
cout << r << endl; // 10:r 绑定的就是 a
}
运行结果:
10
10
三、完美转发
3.1 模板中的 && 是万能引用
cpp
void Fun(int &x) { cout << "左值引用" << endl; }
void Fun(const int &x) { cout << "const 左值引用" << endl; }
void Fun(int &&x) { cout << "右值引用" << endl; }
void Fun(const int &&x) { cout << "const 右值引用" << endl; }
// 模板中的 && 不代表右值引用,而是万能引用,其既能接收左值又能接收右值。
template<typename T>
void PerfectForward(T&& t)
{
Fun(t); // 这里直接把 t 传下去
}
int main()
{
PerfectForward(10); // 右值
int a;
PerfectForward(a); // 左值
PerfectForward(std::move(a)); // 右值
const int b = 8;
PerfectForward(b); // const 左值
PerfectForward(std::move(b)); // const 右值
}
运行结果:
左值引用
左值引用
左值引用
const 左值引用
const 左值引用
💡 为什么传进去的明明是右值,最后全走了左值版本 ?因为
t是一个有名字的变量 ------具名变量本身就是左值 。T&&只是让形参能接收 左右值,一旦进了函数体,t就退化成左值,Fun(t)自然只能匹配左值引用的重载。
3.2 std::forward 完美转发
std::forward 在传参的过程中保留对象原生类型属性:
cpp
template<typename T>
void PerfectForward(T&& t)
{
Fun(std::forward<T>(t)); // 保持 t 的原生类型属性往下传
}
int main()
{
PerfectForward(10); // 右值
int a;
PerfectForward(a); // 左值
PerfectForward(std::move(a)); // 右值
const int b = 8;
PerfectForward(b); // const 左值
PerfectForward(std::move(b)); // const 右值
}
运行结果:
右值引用
左值引用
右值引用
const 左值引用
const 右值引用
| 调用 | 传进来是什么 | 不转发(Fun(t)) |
转发(Fun(std::forward<T>(t))) |
|---|---|---|---|
PerfectForward(10) |
右值 | 左值引用 | 右值引用 |
PerfectForward(a) |
左值 | 左值引用 | 左值引用 |
PerfectForward(std::move(a)) |
右值 | 左值引用 | 右值引用 |
PerfectForward(b) |
const 左值 | const 左值引用 | const 左值引用 |
PerfectForward(std::move(b)) |
const 右值 | const 左值引用 | const 右值引用 |
💡 每一次向下传递,都要重新做一次完美转发 ------因为每经过一层函数,具名的形参又会退化成左值。也就是说:转发不是一次性的属性,而是每传递一层就要再
forward一次。
完美转发总结:函数模板 + 转发引用(T&&)+ std::forward。
它的作用是:在泛型编程中,无损传递实参的类型和值类别。
四、类的新功能
4.1 两个新增的默认成员函数
C++11 新增了两个默认成员函数:移动构造函数 和移动赋值运算符重载。
针对移动构造函数和移动赋值运算符重载,有一些需要注意的点:
如果你没有声明析构函数、拷贝构造、赋值重载、移动构造、移动赋值 ,那么编译器会自动生成一个默认移动构造。
默认生成的移动构造函数:
| 成员类型 | 处理方式 |
|---|---|
| 内置类型 | 直接复制值(源对象不变) |
| 自定义类型成员 | 看它是否实现了移动构造/赋值 :实现了就调用移动 ,没实现就调用拷贝 |
cpp
class Member // 自定义类型成员:自带移动构造
{
public:
Member() { cout << "Member()" << endl; }
Member(const Member&) { cout << "Member(const Member&) -- 拷贝" << endl; }
Member(Member&&) { cout << "Member(Member&&) -- 移动" << endl; }
};
class Test // 不声明任何函数:编译器自动生成【默认移动构造】
{
public:
int GetA() const { return _a; }
private:
int _a = 10; // 内置类型成员
Member _m; // 自定义类型成员
};
int main()
{
Test t1;
cout << "--- Test t2(std::move(t1)); ---" << endl;
Test t2(std::move(t1)); // 调用编译器生成的默认移动构造
cout << "t1._a = " << t1.GetA() << endl; // 内置类型只复制,源对象不变
}
运行结果:
Member()
--- Test t2(std::move(t1)); ---
Member(Member&&) -- 移动
t1._a = 10
4.2 类成员变量初始化
C++11 允许在类定义时给成员变量初始缺省值 ,默认生成的构造函数会使用这些缺省值初始化。
4.3 强制生成默认函数的关键字 default
C++11 可以让你更好地控制要使用的默认函数。假设你要使用某个默认函数,但因为一些原因这个函数没有默认生成 ------比如:我们提供了拷贝构造,就不会生成默认移动构造 了。那么可以用 default 关键字显式指定默认移动构造生成:
cpp
class Person
{
public:
Person(const char* name = "", int age = 0)
: _name(name), _age(age)
{}
Person(const Person& p) // 提供了拷贝构造
: _name(p._name), _age(p._age)
{}
Person(Person&& p) = default; // 显式要回默认移动构造
private:
string _name;
int _age;
};
五、复盘(附答案)
💡 思考题
-
左值引用的短板 :为什么"做参数"和"做返回值"都能省拷贝,但遇到局部对象返回就省不了了?传值返回至少几次拷贝构造?
-
移动构造 :它的本质是什么?相比拷贝构造省在哪?移动之后源对象还能用吗?
-
std::move:它到底做了什么?int&& r = std::move(a);之后a的值变了吗? -
万能引用 :模板里的
T&&和普通的右值引用有什么区别?为什么PerfectForward里直接Fun(t)全走左值版本? -
std::forward:它的作用是什么?为什么说"每向下传递一层就要转发一次"?
📝 答案
-
做参数时形参绑的是外部对象的引用 ,不产生拷贝;做返回值时,如果返回的是函数内局部对象 ,函数一结束它就销毁了,引用会悬空 ,所以只能传值返回 。传值返回会产生临时对象 ,至少 1 次拷贝构造(旧编译器可能两次)。
-
本质是把参数右值的资源窃取过来、占为己有 ------直接接手对方的指针/内存,而不做深拷贝 。相比拷贝构造省掉了分配空间 + 复制全部内容 的开销。移动之后源对象被掏空 、资源已交出去,不应再使用它的值。
-
std::move不搬移任何东西 ,唯一功能是把左值强制转换成右值引用 (本质是static_cast<T&&>)。int&& r = std::move(a);之后a的值没变 ,r只是绑到了a上;真正"搬资源"的是后续的移动构造/移动赋值。 -
普通的
T&&只能绑右值;模板里的T&&是万能引用(转发引用) ,左右值都能收 。PerfectForward里直接Fun(t)全走左值版本,是因为t是有名字的变量、具名变量本身是左值 ------T&&只负责"能接收",进了函数体t就退化成左值了。 -
std::forward<T>(t)在传递时保留t的原生类型和值类别 :左值还是左值、右值还是右值。说"每传递一层都要转发一次",是因为每一层函数的具名形参都会退化成左值 ,想继续保留右值属性就必须在每层 再forward一次。
结语
如果本文对你有帮助,欢迎:
👍 点赞 | ⭐ 收藏 | 👤 关注作者 | 💬 留言交流你的疑问或补充
你的每一次互动都是我继续更新的动力,我们下一篇见!🚀