6.1 左值和右值
区分左值与右值:
看能不能取地址 & 若能取地址则为左值 不能取地址为右值
int x = 1; x++;//这个是右值 ++x;//左值 ++x实现 int tmp = x; x = x+1; return tmp; 返回临时的
主要字符串也是左值 它可以取地址
6.2 左值引用
当我们需要将一个对象作为参数传递给子函数的时候,往往会使用左值引用,因为这样可以免去创建临时对象的操作。非常量左值的引用对象很单纯,它们必须是一个左值。对于这一点,常量左值引用的特性显得更加有趣,它除了能引用左值,还能够引用右值
如
int &a = 10;//报错 const int& a = 5;//常量引用
class X { public:X() {}X(const X&) {}X& operator = (const X&) { return *this; }//这里是用在 x3 = make_X 函数得到是右值 }; X make_x() {return X();//这里返回的是右值 需要用常量引用来接收 就是const X& } int main() {X x1;X x2(x1);X x3(make_x());x3 = make_x(); }
6.3 右值引用
常量左值引用可以绑定右值是一条非常棒的特性,但是它也存在一个很大的缺点——常量性。一旦使用了常量左值引用,就表示我们无法在函数内修改该对象的内容 引出右值引用
右值引用是一种引用右值且只能引用右值的方法
int&& t = 10;
右值引用的特点之一是可以延长右值的生命周期
# include <iostream> class X { public:X() { std::cout << "X ctor" << std::endl; }X(const X& x) { std::cout << "X copy ctor" << std::endl; }~X() { std::cout << "X dtor" << std::endl; }void show() { std::cout << "show X" << std::endl; } }; X make_x() {X x1;cout <<"1111 "<< & x1 << endl;return x1; } int main() {X&& x2 = make_x();//这里不使用右值引用应该会发生三次构造 make_X中 x1一次return 一次 给x2一次//使用右值引用少了最后一次 给最后的临时变量的生命周期加长了 cout << "1111 " << &x2 << endl;x2.show(); }
6.4 右值的性能优化空间
#include <iostream> class BigMemoryPool { public:static const int PoolSize = 4096;BigMemoryPool() : pool_(new char[PoolSize]) {cout << "1111" << endl;}~BigMemoryPool(){if (pool_ != nullptr) {delete[] pool_;}}BigMemoryPool(const BigMemoryPool& other) : pool_(newchar[PoolSize]){std::cout << "copy big memory pool." << std::endl;memcpy(pool_, other.pool_, PoolSize);} private:char* pool_; }; BigMemoryPool get_pool(const BigMemoryPool& pool) {return pool; } BigMemoryPool make_pool() {BigMemoryPool pool;return get_pool(pool); } int main() {BigMemoryPool my_pool = make_pool(); }
以上代码同样GCC需要加上编译参数-fno-elideconstructors
用VS的好像不能这样
这里会调用三次拷贝构造 get一个 make一个 最后 main函数返回值一个
还有很大的优化空间
6.5 移动语义
class BigMemoryPool { public:static const int PoolSize = 4096;BigMemoryPool() : pool_(new char[PoolSize]) {}~BigMemoryPool(){if (pool_ != nullptr) {delete[] pool_;}}BigMemoryPool(BigMemoryPool&& other){std::cout << "move big memory pool." << std::endl;pool_ = other.pool_;other.pool_ = nullptr;}BigMemoryPool(const BigMemoryPool& other) : pool_(newchar[PoolSize]){std::cout << "copy big memory pool." << std::endl;memcpy(pool_, other.pool_, PoolSize);} private:char* pool_; }; BigMemoryPool get_pool(const BigMemoryPool& pool) {return pool; } BigMemoryPool make_pool() {BigMemoryPool pool;return get_pool(pool); } int main() {BigMemoryPool my_pool = make_pool(); }
加入移动语句 这样后面两次的拷贝就变成了移动
6.6 值类别
对于将亡值(xvalue),读者实际上只需要知道它是泛左值和右值交集即可
这里咕噜咕噜一大堆 等后边用到这些再回来看
6.7 将左值转换为右值
int a = 10; int&& x = a ;//error 我们知道右值引用不能绑定左值 int&& x = static_cast<int&&>(a); //这样子就行 显式的将左值转为将亡值 它的最大作用是让左值使用移动语义BigMemoryPool my_pool1; BigMemoryPool my_pool2 = my_pool1; BigMemoryPool my_pool3 = static_cast<BigMemoryPool &&>(my_pool1); //my_pool1是左值 static_cast将它变为将亡值 可以使用移动语句 但是 后续不能对my_pool1操作了
无论一个函数的实参是左值还是右值,其形参都是一个左值,即使这个形参看上去是一个右值引用
void move_pool(BigMemoryPool &&pool) {std::cout << "call move_pool" << std::endl;BigMemoryPool my_pool(pool);//这里是左值调用拷贝构造 } int main() {move_pool(make_pool()); }
要想用移动构造
void move_pool(BigMemoryPool &&pool) {std::cout << "call move_pool" << std::endl;BigMemoryPool my_pool( static_cast<BigMemoryPool &&>pool);//这里是右值调用移动构造 } int main() {move_pool(make_pool()); }
将左值变为右值 还有move函数 内部就是用static_cast 只是它是模板函数 不需要直接定义类型 方便
6.8 万能引用和引用折叠
万能引用长的和右值引用一样 都是&& 若是有类型的推导就是万能引用 若是没有类型推导就是右值引用
int&& a = 10;//右值引用 auto && x = 10//万能引用
在模板当中&&含有类型推导都是万能引用 既可以用左值也可以用右值
使用万能引用右引用折叠 就是一堆的&&&这个
所有右值引用折叠到右值引用上仍然是一个右值引用。(A&& && 变成 A&&)
2.所有的其他引用类型之间的折叠都将变成左值引用。 (A& & 变成 A&; A& && 变成 A&; A&& & 变成 A&)只要有左值引用参与进来,最后推导的结果就是一个左值引用。只有实际类型是一个非引用类型或者右值引用类型时,最后推导出来的才是一个右值引用。
万能引用的形式必须是T&&或者auto&&,也就是说它们必须在初始化的时候被直接推导出来
#include <vector> template<class T> void foo(std::vector<T>&& t) {} int main() {std::vector<int> v{ 1,2,3 };foo(v); // 编译错误 }
6.9 完美转发
与万能引用结合 : 万能引用用途
用于模板当中 用于传递参数 给调用函数 它真正的值类别
为什么呢
当我们在模板函数中传递参数时,通常会用泛型
T
来接收传入的参数。但是,如果我们传递的是右值而没有使用完美转发,右值的状态会被“降级”成左值,这意味着移动语义失效,编译器会认为这是一个左值并调用拷贝构造函数,而不是移动构造函数。#include <iostream> #include <utility> #include <string>// 一个简单的类,用于模拟资源 class MyResource { public:std::string name;MyResource(const std::string& n) : name(n) {std::cout << "Constructing " << name << std::endl;}MyResource(MyResource&& other) noexcept : name(std::move(other.name)) {std::cout << "Move constructing " << name << std::endl;}MyResource(const MyResource& other) : name(other.name) {std::cout << "Copy constructing " << name << std::endl;} };// 这个函数接受任意类型的参数,并将它完美转发给另一个函数 template<typename T> void passThrough(T&& arg) {std::cout << "passThrough called" << std::endl;process(std::forward<T>(arg)); // 完美转发 }// 用来处理传入的参数 void process(const MyResource& res) {std::cout << "Process by const lvalue reference: " << res.name << std::endl; }void process(MyResource&& res) {std::cout << "Process by rvalue reference: " << res.name << std::endl; }int main() {MyResource res1("Resource1"); // 创建一个对象passThrough(res1); // 传入左值,应该调用 lvalue 版本的 processpassThrough(MyResource("Resource2")); // 传入右值,应该调用 rvalue 版本的 processpassThrough(std::move(res1)); // 显式地将 res1 转换为右值 }
6.10 针对局部变量和右值引用的隐式移动操作
c++20的用法略