移动语义
-
移动语义主要适用于自定义的类型,其中移动操作可以显著减少资源的拷贝成本。
-
当需要将一个对象转移到一个新的智能指针或容器中时,可以使用移动语义来避免不必要的拷贝操作,提高性能和效率。
-
右值引用变量不再拥有原始资源的所有权。资源的所有权已经转移到其他对象(如移动赋值目标),因此在使用右值引用之后,我们应该小心避免对其进行访问,以免出现悬空引用或无效操作。
-
左值和右值是C++中非常重要的概念,理解它们有助于更好地理解语言的基本语义和编程模型。下面将详细解释左值和右值的用法,并提供示例来帮助你学习它们。
1 左值和右值
左值(Lvalue):
可以修改的值
-
变量: 变量是最常见的左值,因为它们具有名称,可以用于存储和修改值。
int x = 10; // x 是左值 int y = 20; int c = x + y; // x+y为右值,c为左值 -
数组元素: 数组元素也是左值,因为它们可以通过索引修改。
int arr[5] = {1, 2, 3, 4, 5}; arr[0] = 42; // arr[0] 是左值 -
引用: 引用也是左值,因为它们是变量的别名,可以用于修改原始变量的值。
int y = 20; int& ref = y; // ref 是左值,可以修改 y 的值 int && ref2 = std::move(y) // ref2 是左值 ref = 30; // 修改了 y 的值 -
函数返回的左值: 如果函数返回的是一个具体的值(而不是临时对象或表达式的结果),那么它是左值。
int getValue() { return 100; } int result = getValue(); // result 是左值
右值(Rvalue):
-
纯右值 : 基本类型的常量或者临时对象
-
将亡值: 自定义类型的临时对象
右值是临时的、不可修改的值,通常表示计算结果或临时对象。右值是可以放在赋值运算符的右边的值。右值通常用于初始化变量或作为函数参数。
-
字面常量: 字面常量是右值,因为它们是固定的、不可修改的值。
int a = 42; // 42 是右值 -
函数的返回值(临时对象): 当创建临时对象时,它们通常是右值。
std::string getName() { return "Alice"; } std::string greeting = "Hello, " + getName(); // "Hello, Alice" 是右值 -
表达式的返回值(临时对象): 复杂表达式的结果通常是右值,因为它们是计算的临时值。
int sum = 5 + 3; // (5 + 3) 是右值 -
std::move返回的右值引用:std::move函数将左值转换为右值引用,返回的是右值引用。int x = 42; int&& rvalueRef = std::move(x); // std::move(x) 是右值, rvalueRef是右值引用,但是本身为左值,可以修改x
左值和右值在C++中的区分非常重要,因为它们在赋值、传递参数、函数返回值和移动语义等方面具有不同的语义。理解它们有助于编写更安全和高效的C++代码。
-
2 左值引用、右值引用
- 左值引用大部分引用左值
int a = 10;
int &b = a;
- const左值引用可以引用右值(常量)
const int &a = 10;
const int &b = x + y;
- 右值引用大部分引用右值
int &&a = 10;
int &&b = x + y;
- 通过move语义可以引用左值
int &&c = move(a);
3 比较
左值引用:
- 做参数:减少传参时的拷贝
- 做返回值:返回自身,减少拷贝
右值引用:
- 用来写类的移动构造和移动赋值,进而在类的使用过程中,减少拷贝次数
- 做参数:减少传参时的拷贝
- 函数内:通过调用移动构造,减少拷贝次数
- 做返回值:返回自身,减少拷贝
- 移动赋值、减少拷贝次数
4 右值引用的应用(2个)
- 类的移动拷贝和移动赋值
class string
{
string(const char* str = "")
:_size(strlen(str))
, _capacity(_size)
{
//cout << "string(char* str)" << endl;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
// s1.swap(s2)
void swap(string& s)
{
::swap(_str, s._str);
::swap(_size, s._size);
::swap(_capacity, s._capacity);
}
// 拷贝构造
string(const string& s)
:_str(nullptr)
{
cout << "string(const string& s) -- 深拷贝" << endl;
string tmp(s._str);
swap(tmp);
}
// 赋值重载
string& operator=(const string& s)
{
cout << "string& operator=(string s) -- 深拷贝" << endl;
string tmp(s);
swap(tmp);
return *this;
}
// 移动构造
string(string&& s)
:_str(nullptr)
,_size(0)
,_capacity(0)
{
cout << "string(string&& s) -- 移动语义" << endl;
swap(s);
}
// 移动赋值
string& operator=(string&& s)
{
cout << "string& operator=(string&& s) -- 移动语义" << endl;
swap(s);
return *this;
}
-
- 模板父函数通过万能音乐和完美转发接收参数
- 子函数函数重载 左值引用和右值引用
// 模板父函数 template<class T> void PushBack(T&& x) { //Insert(_head, x); Insert(_head, std::forward<T>(x)); } void PushFront(T&& x) { //Insert(_head->_next, x); Insert(_head->_next, std::forward<T>(x)); } // 子函数重载 void Insert(Node* pos, T&& x) { Node* prev = pos->_prev; Node* newnode = new Node; newnode->_data = std::forward<T>(x); // 关键位置 // prev newnode pos prev->_next = newnode; newnode->_prev = prev; newnode->_next = pos; pos->_prev = newnode; } void Insert(Node* pos, const T& x) { Node* prev = pos->_prev; Node* newnode = new Node; newnode->_data = x; // 关键位置 // prev newnode pos prev->_next = newnode; newnode->_prev = prev; newnode->_next = pos; pos->_prev = newnode; }
5 万能引用和完美转发
万能引用是一种特殊类型的引用,它可以接受任意类型的值,包括左值和右值。
在C++中,万能引用通常使用&&符号来声明,就像右值引用一样。
万能引用通常与模板一起使用,例如:
template<typename T>
void foo(T&& t) {
// ...
}
在这个例子中,T&&就是一个万能引用。这个函数模板foo接受一个参数t,它可能是左值也可能是右值。在函数体内部,你可以根据t的具体类型来决定如何处理它。
万能引用通常与模板推导结合使用,这意味着编译器会根据传递给函数的实参类型来推导出t的类型。如果传递给foo的是左值,T会被推导为左值引用类型;如果传递给foo的是右值,T会被推导为非引用类型。
完美转发:右值引用在第二次传参的过程中,右值属性会消失,因此需要完美转发 std::forward<T>(t)来保持属性不变
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; }
// 1 主函数,通过万能引用接收参数,自动推断为左右值引用
template<typename T>
void PerfectForward(T&& t)
{
// 2 左值引用调用左值对应的函数,右值引用调用右值的函数,使用完美转发保持右值属性,否则属性失效,全部调用左值函数
Fun(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 右值
return 0;
}