定义
左值 / 左值引用
左值(Lvalue): 左值是一个表示数据的表达式(如变量名或解引用的指针),我们可以 对它取地址 + 可以对它赋值,左值可以出现赋值符号的左边,右值不能出现在赋值符号左边。定义时const修饰符后的左值,不能给他赋值,但是可以取它的地址。
左值引用:就是给左值的引用,给左值取别名。
例子:
// 变量 a, b、指针变量 p 和解引用表达式 *p 都是左值
int* p = new int(0);
int a = 1;
const int b = 2;
// 下面是对上述左值的引用
int*& rp = p;
int& ra = a;
const int& rb = b;
int& pValue = *p;
右值 / 右值引用
右值(Lvalue):右值也是一个表示数据的表达式,如:字面常量、表达式返回值,函数返回值(这个不能是左值引用返回)等等,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能取地址。
右值引用:就是对右值的引用,给右值取别名
double x = 1.0, y = 2.5;
// 几种常见的右值
10;
x + y;
fmax(x, y);
// cout << &(x + y) << endl; //对右值不能进行取地址
//以下是对上述右值的右值引用
int&& rr1 = 10;
double&& rr2 = x + y;
double&& rr3 = fmax(x, y);
左值引用 / 右值引用 特点比较
左值引用:
- 左值引用只能引用左值,不能引用右值。
- 但是const左值引用既可引用左值,也可引用右值
// 左值引用只能引用左值,不能引用右值
int a = 10;
int& ra1 = a;
int& ra2 = 10; //编译失败,10是右值
// const左值引用既可以引用左值,也可以引用右值
const int& ra3 = a;
const int& ra4 = 10; // 不报错
右值引用:
- 右值引用只能右值,不能引用左值。
- 但是右值引用可以move以后的左值。
// 右值引用只能引用右值,不能引用左值
int&& r1 = 10;
int a = 10;
int&& r2 = a; //编译失败: 无法将右值引用绑定到左值
// 右值引用可以引用 move 后的左值
int&& r3 = std::move(a);
右值引用的应用场景 和 意义
上文了解到 const左值引用 可以引用右值,既然如此,右值引用的意义是什么?
实际上,左值引用有一定的短板,而右值引用可以弥补该缺陷:
首先看下面的代码:
namespace aiyimu
{
class string
{
public:
// 构造函数
string(const char* str = "")
:_size(strlen(str))
, _capacity(_size)
{
_str = new char[_capacity + 1];
strcpy(_str, str);
}
void swap(string& s)
{
::swap(_str, s._str); //std::swap -> ::swap
::swap(_size, s._size);
::swap(_capacity, s._capacity);
}
// 拷贝构造
string(const string& s)
:_str(nullptr)
{
cout << "string(const string& s) <---> 拷贝构造(深拷贝)" << endl;
// 拷贝操作
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
// 移动构造
// 以右值引用作参数
string(string&& s)
:_str(nullptr)
, _size(0)
, _capacity(0)
{
cout << "string(string&& s) <---> 资源转移" << endl;
swap(s);
}
// 拷贝赋值运算符
string& operator=(const string& s)
{
cout << "string& operator=(string s) <---> 拷贝赋值(深拷贝)" << endl;
string tmp(s);
swap(tmp);
return *this;
}
// 移动赋值运算符
string& operator=(string&& s)
{
cout << "string& operator=(string&& s) <---> 移动赋值(资源移动)" << endl;
swap(s);
return *this;
}
// 析构
~string()
{
delete[] _str;
_str = nullptr;
}
private:
char* _str;
size_t _size;
size_t _capacity;
};
}
移动构造函数
定义:
移动构造(Move Constructor)是一种特殊的构造函数,它通过接收一个右值引用参数来创建新对象,并从传入的对象中“移动”资源而不是执行深拷贝。
详解:
我们在代码实现中 写了移动构造函数,与右值引用有一定的联系,详解在这里:
移动构造函数
在 引用构造 的博客中有具体写到:
- 由于我们实现的 拷贝构造 的参数是const 类型,所以既可以进行左值引用也可以进行右值引用 。
- 当存在移动构造时,传入右值优先调用移动构造,否则构造此时的拷贝构造。
- 对上面的代码,to_string 的 参数是 右值,调用移动构造,但我们讲两种构造都作讨论。
认识了移动构造函数后,对下面的代码:
aiyimu::string to_string(int value)
{
aiyimu::string str;
// to_string的内容并不重要
// ... ... 这里省略
return str;
}
int main()
{
aiyimu::string ret("114");
ret = to_string(514);
return 0;
}
当执行的是拷贝构造+拷贝赋值时:
可以看出拷贝构造时 进行了两次深拷贝
而当执行的是移动构造+移动赋值时:
此时的右值引用 避免了两次深拷贝,避免了空间浪费。
可以看出 右值引用 配合 移动构造和移动赋值 的作用。
万能引用(引用折叠)
万能引用(Universal Reference)是由C++中的std::forward
和模板推导机制引入的概念。它是一种特殊类型的引用,可以接受各种类型的参数(包括左值、右值)。
定义
首先,在C++中,几乎所有的标准 容器类都支持万能引用 的用法。
- 如果传递给万能引用的是一个左值,那么万能引用将被推导为左值引用。这意味着它将保留传递给它的参数的左值特性,并且不能绑定到临时对象或右值。
例如:
int x = 42;
int& lvalueRef = x; // 左值引用
foo(lvalueRef); // T 被推导为 int&
- 如果传递给万能引用的是一个右值,那么万能引用将被推导为普通的右值引用。这意味着它可以绑定到临时对象、右值或将对象视为右值的情况下。
例如:
int&& rvalueRef = 100; // 普通的右值引用
foo(std::move(rvalueRef)); // T 被推导为 int&&
请注意,万能引用只能 通过模板来声明 。在函数模板中使用万能引用的时候,根据传递的参数类型和值类别,编译器会进行相应的类型推导。
查阅发现,C++11
在list类中添加了万能引用的实现。
声明
万能引用的格式是使用&&
语法来声明,它的一般形式为:
template<typename T>
void foo(T&& arg) {
// 根据 arg 的值类别进行不同的处理
if constexpr (std::is_lvalue_reference_v<T>) {
// 处理左值
std::cout << "左值引用" << std::endl;
} else {
// 处理右值
std::cout << "右值引用" << std::endl;
}
}
用法
下面的代码展示万能引用的用法:
// 万能引用
void Func(int& x) { cout << "左值引用" << endl; }
void Func(const int& x) { cout << "const 左值引用" << endl; }
void Func(int&& x) { cout << "右值引用" << endl; }
void Func(const int&& x) { cout << "const 右值引用" << endl; }
template<typename T>
void PerfectForward(T&& t)
{
// 完美转发: 保持t引用对象属性
Func(std::forward<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 右值
aiyimu::list<aiyimu::string> lt;
aiyimu::string s1("hello");
lt.push_back(s1);
cout << endl;
//lt.push_back(aiyimu::string("world"));
lt.push_back("world");
return 0;
}
根据执行结果,可以验证万能引用的作用。