1. 左值、右值、左值引用和右值引用
C++98中就有引用语法了(左值引用),C++11中又增加了右值引用的语法特性。
1.1 左值和右值
左值和右值的区别如下:
- 左值
可以取地址,右值不能取地址 - 左值在内存中存储,右值一般存在栈 / 寄存器 / 临时位置中
常见的左值和右值:
cpp
//左值
int* p = new int(0);
int b = 1;
const int c = b;
*p = 10;
string s("111111");
s[0] = 'x';
//右值
double x = 1.1, y = 2.2;
10;
fmin(x + y);
string("11111");
1.2 左值引用和右值引用
左值引用是给左值取别名,右值引用同理。
cpp
//左值引用
string& si = s;
int*& pi = p;
//右值引用
double&& z = x + y;
int&& num = 10;
左值引用在特定条件下可以绑定右值,右值引用同样也能绑定左值:

cpp
const int& ci = 10; //左值引用->右值(权限缩小)
double&& f = move(x); //右值引用->左值
- move内部进行了强制类型转换和引用折叠,使得左值可以转化为右值返回
- 左值引用和右值引用的底层都是
指针 - 右值引用和const左值引用可以为临时对象
延长生命周期
cpp
//延长生命周期
int main()
{
string s1 = "test";
const string& t1 = s1 + s1;
string&& t2 = s1 + s1;
cout << t1 << endl;
cout << t2 << endl;
return 0;
}
1.3 参数匹配
左值和右值还支持参数匹配,其中右值引用的参数类型是左值。可以通过函数重载来测试:
cpp
//参数匹配
void f(int& x)
{
std::cout << "左值引用重载 f(" << x << ")\n";
}
void f(const int& x)
{
std::cout << "const左值引用重载 f(" << x << ")\n";
}
void f(int&& x)
{
std::cout << "右值引用重载 f(" << x << ")\n";
}
int main()
{
int x = 10;
int y = 20;
const int z = 30;
int&& t = 40; //右值引用的参数类型是左值
f(x);
f(15);
f(z);
f(std::move(x));
f(t);
return 0;
}
输出结果:

1.4 总结
引用的价值:
- 减少拷贝,提高效率
- 左值引用还能修改实参,使用方便
左值引用的短板:
局部对象出了函数作用域就销毁,不能返回其左值引用,只能传值返回(有拷贝开销)。
三种解决方案:
- 输出型参数:结果通过参数带出 ------ 牺牲可读性
- 编译器优化(RVO):省略拷贝,但非标准保证
- 右值引用 + 移动语义(C++11):
标准方案,直接掠夺临时对象资源,避免深拷贝
2. 右值引用和移动语义
2.1 移动构造和移动赋值
- 参数都是
右值引用 - 都是为
右值服务,可以省去拷贝构造,直接掠夺资源(偷指针后置空),提升效率
cpp
// 移动构造
string(string&& s)
{
cout << "string(string&& s) -- 移动构造" << endl;
swap(s);
}
// 移动赋值
string& operator=(string&& s)
{
cout << "string& operator=(string&& s) -- 移动赋值" << endl;
swap(s);
return *this;
}
2.2 移动语义对传值返回和传参的优化
我们以自己实现的string类和addStrings函数为例:
cpp
namespace mzh
{
class string
{
public:
typedef char* iterator;
typedef const char* const_iterator;
friend ostream& operator<<(ostream& out, const string& s);
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str + _size;
}
string(const char* str = "")
:_size(strlen(str))
, _capacity(_size)
{
cout << "string(char* str)-构造" << endl;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
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;
reserve(s._capacity);
for (auto ch : s)
{
push_back(ch);
}
}
// 移动构造
string(string&& s)
{
cout << "string(string&& s) -- 移动构造" << endl;
swap(s);
}
//拷贝赋值
string& operator=(const string& s)
{
cout << "string& operator=(const string& s) -- 拷贝赋值" << endl;
if (this != &s)
{
_str[0] = '\0';
_size = 0;
reserve(s._capacity);
for (auto ch : s)
{
push_back(ch);
}
}
return *this;
}
// 移动赋值
string& operator=(string&& s)
{
cout << "string& operator=(string&& s) -- 移动赋值" << endl;
swap(s);
return *this;
}
~string()
{
// cout << "~string() -- 析构" << endl;
delete[] _str;
_str = nullptr;
}
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
void reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
if (_str)
{
strcpy(tmp, _str);
delete[] _str;
}
_str = tmp;
_capacity = n;
}
}
void push_back(char ch)
{
if (_size >= _capacity)
{
size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
reserve(newcapacity);
}
_str[_size] = ch;
++_size;
_str[_size] = '\0';
}
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
const char* c_str() const
{
return _str;
}
size_t size() const
{
return _size;
}
private:
char* _str = nullptr;
size_t _size = 0;
size_t _capacity = 0;
};
ostream& operator<<(ostream& out, const string& s)
{
for(size_t i=0; i<s._size; i++)
{
out << s._str[i];
}
return out;
}
}
cpp
namespace mzh
{
string addStrings(string num1, string num2)
{
string str;
int end1 = num1.size() - 1, end2 = num2.size() - 1;
int next = 0;
while (end1 >= 0 || end2 >= 0)
{
int val1 = end1 >= 0 ? num1[end1--] - '0' : 0;
int val2 = end2 >= 0 ? num2[end2--] - '0' : 0;
int ret = val1 + val2 + next;
next = ret / 10;
ret = ret % 10;
str += ('0' + ret);
}
if (next == 1)
str += '1';
reverse(str.begin(), str.end());
cout << "******************************" << endl;
return str;
}
}
2.2.1 移动构造的优化
示例main函数:
cpp
int main()
{
mzh::string ret = mzh::addStrings("11111", "2222");
cout << ret.c_str() << endl;
return 0;
}
无移动构造:

有移动构造:

2.2.1 移动赋值的优化
示例main函数:
cpp
//场景2
int main()
{
mzh::string ret; //1构造
ret = mzh::addStrings("11111", "2222");
cout << ret.c_str() << endl;
return 0;
}
无移动赋值:

有移动赋值:

2.3 总结
C++11 前后对比:
- 前:传值返回依赖
编译器优化,非标准保证 - 后:语言标准提供移动构造/移动赋值,编译器优化可有可无,差别不大;STL容器新增了移动构造/移动赋值,大大提效
值得实现移动语义的类型:
深拷贝类(如vector/string/map...):实现移动构造和移动赋值,价值很大浅拷贝类(如Date/pair<int,int>...):不需要实现,代价不大
3. 类型分类
文档:值类别
右值分类:
- 纯右值(无地址)
- 将亡值(
有地址,可以偷指针)
4. 引用折叠
通过模板或者typedef可以实现引用的引用:
cpp
template<class T>
void f1(T& x)
{}
template<class T>
void f2(T&& x)
{}
int main()
{
typedef int& lref;
typedef int&& rref;
int n = 0;
lref& r1 = n; //左 + 左 = 左
lref&& r2 = n; //左 + 右 = 左
rref& r3 = n; //右 + 左 = 左
rref&& r4 = 1; //右 + 右 = 右
}
- & & → &
- & && → &
- && & → &
- && && → &&
总结:只要有左值引用参与,就是左值引用;只有两个右值引用叠加时才是右值引用。
引用折叠在实践中常用于函数自动推导类型:
cpp
//引用折叠/万能引用
template<class T>
void Function(T&& t)
{
int a = 0;
T x = a;
cout << &a << endl;
cout << &x << endl;
cout << endl;
}
int main()
{
Function(10); //右值:T为int
int a;
Function(a); //左值:T为int&
Function(std::move(a)); //右值:T为int
const int b = 8;
Function(b); //左值:T为const int&
Function(std::move(b)); //右值:T为const int
}
万能引用 :Function函数在实例化后,参数可以是左值引用,也可以是右值引用
5. 完美转发
在使用万能引用时,右值引用的参数可能需要继续向下一个函数传递:
cpp
void push_back(const string& s) // 左值版本
{
// 拷贝构造插入
}
void push_back(string&& s) // 右值版本
{
// 插入时必须 move/forward,否则 s 在本函数内是左值
insert(end(), move(s)); //转发为右值
// insert(end(), s); //丢失右值属性
}
完美转发 :使用forward模板进行转发,保持参数属性不变,本质通过引用折叠实现
。模板如下:
cpp
template <class T> T&& forward (typename remove_reference<T>::type& arg);
template <class T> T&& forward (typenameremove_reference<T>::type&& arg);
测试示例:
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<class T>
void Function(T&& t)
{
// Fun(t);
Fun(std::forward<T>(t)); //完美转发
}
int main()
{
Function(10); // 右值
int a;
Function(a); // 左值
Function(std::move(a)); // 右值
const int b = 8;
Function(b); // const左值
Function(std::move(b)); // const右值
return 0;
}