C++ 右值引用 && 的用法
左值和右值
c++ 中值分为左值和右值,通常来讲左值定义位于等号左边,可以取地址或赋值给引用。右值位于等号右边,不能取地址,并且只能赋值给常引用。
比如:
int a = 1;
其中 a 是左值,1 是右值常量。
引用的使用方式:
int& b = a; // 正确
int& c = 1; // 编译错误,不能将右值赋值给引用(符合直觉,引用是变量的别名,可以改变变量值,如果是常量没有意义)。
const int& c = 1; // 正确,右值可以赋值给常引用。
左值引用和右值引用
左值引用 上面已经出现过,使用 &
符号定义:
int a = 1;
int& b = a;
右值引用 引用的是右值,使用 &&
符号定义:
int a = 1;
int&& b = a; // 编译错误,左值不能赋给右值引用。
int&& b = 1; // 正确
如果想定义变量 a 的右值引用,可以用 std::move
或者 std:forward
函数,将变量 a 转化为右值,如下:
int a = 1;
int&& b = move(a); // 正确,move 函数将左值 a 转为右值。
int&& b = forward<int>(a); // 正确,forward 将左值转为右值。
std::forward<T>(arg)
有两个参数:T 与 arg,大致用法:
- 当 T 为左值引用类型时,arg 将被转换为 T 类型的左值。
- 否则 arg 将被转换为 T 类型右值。
右值引用的作用
c++11 引入右值引用,主要解决对象拷贝(比如动态分配的对象、指针指向的数据等)时避免深拷贝,将原有的动态数据移动到新的对象,提高运行效率。以下用一个自定义的 String 类拷贝为例(代码非原创,来自其它地方):
#include <iostream>
#include <string>
#include <cstring>
using namespace std;
class String
{
public:
char* str;
String() : str(new char[1])
{
cout << "调用默认空构造函数" << endl;
str[0] = 0;
}
// 构造函数
String(const char* s)
{
cout << "调用构造函数" << endl;
int len = strlen(s) + 1;
str = new char[len];
strcpy_s(str, len, s);
}
// 复制构造函数
String(const String & s)
{
cout << "调用复制构造函数" << endl;
int len = strlen(s.str) + 1;
str = new char[len];
strcpy_s(str, len, s.str);
}
// 复制赋值运算符
String & operator = (const String & s)
{
cout << "调用复制赋值运算符" << endl;
if (str != s.str)
{
delete[] str;
int len = strlen(s.str) + 1;
str = new char[len];
strcpy_s(str, len, s.str);
}
return *this;
}
// 移动构造函数
// 和复制构造函数的区别在于,其参数是右值引用
String(String && s) : str(s.str)
{
cout << "调用移动构造函数" << endl;
// 清理原数据引用,防止多处引用同一数据,易出错。
s.str = nullptr;
}
// 移动赋值运算符
// 和复制赋值运算符的区别在于,其参数是右值引用
String & operator = (String && s)
{
cout << "调用移动赋值运算符" << endl;
if (str != s.str)
{
// 只复制了数据的指针地址
str = s.str;
// 清理原数据地址变量
s.str = nullptr;
}
return *this;
}
// 析构函数
~String()
{
delete[] str;
}
};
template <class T>
void MoveSwap(T & a, T & b)
{
T tmp = move(a); //std::move(a) 为右值,这里会调用移动构造函数
a = move(b); //move(b) 为右值,因此这里会调用移动赋值运算符
b = move(tmp); //move(tmp) 为右值,因此这里会调用移动赋值运算符
}
template <class T>
void Swap(T & a, T & b)
{
T tmp = a; //调用复制构造函数
a = b; //调用复制赋值运算符
b = tmp; //调用复制赋值运算符
}
int main()
{
// 调用复制构造函数
String s("hello");
cout << "s: " << s.str << endl;
// 调用空构造函数
String s1;
// 调用带参构造函数
// 调用移动赋值函数,如果未定义移动赋值函数,则会调用普通赋值函数(引起深拷贝)。
s1 = String("world");
cout << "s1: " << s1.str << endl;
// 调用移动构造函数
String s2(move(s1));
cout << "s2: " << s2.str << endl;
// 调用移动赋值函数
String s3;
s3 = "hello";
cout << "s3: " << s3.str << endl;
// MoveSwap 相比 Swap 减少多次数据赋值
cout << "===============测试 swap ================" << endl;
String p1 = "hello", p2 = "world";
cout << "=====复制 swap======" << endl;
Swap(p1, p2);
cout<< "=====移动 swap====" << endl;
MoveSwap(p1, p2);
system("pause");
return 0;
}
输出结果:
调用构造函数
s: hello
调用默认空构造函数
调用构造函数
调用移动赋值运算符
s1: world
调用移动构造函数
s2: world
调用默认空构造函数
调用构造函数
调用移动赋值运算符
s3: hello
===============测试 swap ================
调用构造函数
调用构造函数
=====复制 swap======
调用复制构造函数
调用复制赋值运算符
调用复制赋值运算符
=====移动 swap====
调用移动构造函数
调用移动赋值运算符
调用移动赋值运算符
调用移动构造函数或移动赋值函数,避免了 String 类型中 str 成员的数据拷贝。
标准库 vector 中的右值引用
std::vector 的 push_back
函数使用了右值引用:
void push_back(const _Ty& _Val) { // insert element at end, provide strong guarantee
emplace_back(_Val);
}
void push_back(_Ty&& _Val) {
// insert by moving into element at end, provide strong guarantee
// 这里注意下,参数方式传入的 _Val 是右值引用,但作为变量它是左值,因而通过 move 函数转为右值。
emplace_back(_STD move(_Val));
}
template <class... _Valty>
_CONSTEXPR20 decltype(auto) emplace_back(_Valty&&... _Val) {
// insert by perfectly forwarding into element at end, provide strong guarantee
_Ty& _Result = _Emplace_one_at_back(_STD forward<_Valty>(_Val)...);
#if _HAS_CXX17
return _Result;
#else // ^^^ _HAS_CXX17 ^^^ // vvv !_HAS_CXX17 vvv
(void) _Result;
#endif // _HAS_CXX17
}
0 条评论