1.左值引用和右值引用
左值是某种存储支持的变量,右值是临时变量或可读但没有固定寻址方法的变量。
左值有具体的地址可以访问,右值有地址但没有固定寻址方法,右值是一种优化技巧。
左值引用只能接受左值,若加上const修饰就可以接收右值,但实际上用const修饰以后左值引用的变量以后,引用得来的变量就变成了一个不可修改的右值。
右值分纯右值,和将亡值。纯右值是只字面值、算数表达式、返回非引用类型的函数调用等等的数据值;将亡值是c++11新引入的类型与右值引用(移动语义)相关的值类型。
用在函数传参和函数返回值时,左值引用避免了对象的拷贝
void get(int& a)//只接收左值,get(10)会报错,因为10是一个右值
void get(const int& a)//可以接收左值和右值,但是a无法再被修改
右值引用只能接受右值,语法上是再加上一个&,右值引用的好处是,当我们输入值为右值时,我们知道它只是一个临时创建的变量,我们可以放心对其进行修改。
右值引用实现了移动语义和完美转发
void get(int&& x)//右值引用,get(10)也不会报错
void get_string1(string&& s)
{
s = "修改";
}
void get_string2(string&& s)
{
string* change;
change = &s;
*change = "硬改";
}
int main()
{
string s1 = "不";
string s2 = "修改";
//get_string(s1) 报错,因为s1是一个左值
get_string1(s1+s2);
cout << s1 + s2 <<endl;
get_string2(s1+s2);
cout << s1 + s2 <<endl;//输出还是不修改,右值引用不会对外面产生任何影响
}
虽然右值无法直接访问地址,但是确实有地址的,可以通过间接的方式获取其内存地址
void get(int&& x,int*& i)
{
i = &x;
cout << i << endl;
}
int main()
{
int* i = nullptr;
get(10,i);
cout << i << endl;
cout << *i << endl;
}
右值引用可以用在move语义的状态,实现简单的move效果
class String {
public:
String() = default;
String(const char* string)
{
std::cout << "create!" << std::endl;
m_Size = strlen(string);
m_Data = new char[m_Size];
memcpy(m_Data, string, m_Size);
}
String(const String& other)
{
std::cout << "copy!" << std::endl;
m_Size = other.m_Size;
m_Data = new char[m_Size];
memcpy(m_Data, other.m_Data, m_Size);
}
String(String&& other) noexcept
{
std::cout << "move!" << std::endl;
m_Size = other.m_Size;
m_Data = other.m_Data;
other.m_Size = 0;
other.m_Data = nullptr;
}
~String()
{
std::cout << "delete!" << std::endl;
delete m_Data;
}
void print() const
{
for (int i = 0; i < m_Size; i++) printf("%c", m_Data[i]);
printf("\n");
}
private:
char* m_Data;
int m_Size;
};
class A {
public:
A() = default;
A(const String& s):s(s){}
A(String&& s) :s((String&&)s) {}
void print()
{
s.print();
}
private:
String s;
};
如果不使用右值引用的拷贝构造函数,在A a(“拷贝”)这样的对象实例化的时候,实际上先调用了有参构造函数,再调用了拷贝构造函数。
开辟了两个堆再依次销毁,浪费性能,那么我们可以使用右值引用实现一个简单的move效果(实际上做了一个浅拷贝的工作)。
A(String&& s) :s(std::move(s)) {}//(String&&)很少用,一般直接用move
move()函数实际上就是将左值转为右值。
在不用右值引用时,函数模板不能完美地转发给内部调用函数,要么只能给内部的其他函数,被转发的参数只能是右值或左值其中之一,不能达到泛型的目的,使用右值引用后就能解决这个问题,达到完美转发。
完美转发实现函数时std::forward,std::forward可以保持原来的值属性不变。
#include <iostream>
void reference(int &v){
printf("左值\n");
}
void reference(int &&v){
printf("右值\n");
}
template <typename T>
void pass(T && v){
reference(v);
reference(std::forward<T>(v));
}
std::forward内部实现:
template<typename _Tp>
constexpr _Tp&&forward(typename std::remove_reference<_Tp>::type& __t) noexcept{
return static_cast<_Tp&&>(__t);
}
template<typename _Tp>
constexpr _Tp&&forward(typename std::remove_reference<_Tp>::type&& __t)noexcept{
static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument substituting _Tp is an lvalue reference type");
return static_cast<_Tp&&>(__t);
}
2.auto和decltype操作符
C++中auto是根据用户初始化内容自动推导类型
- 使用auto时必须初始化
- auto变量不能作为自定义类型的成员变量
- auto不能声明数组
- 模板实例化类型不能是auto类型
- auto作为函数返回值类型时,需要额外再次指定返回类型,如:auto get(int a,double b)->int
decltype可以用来获取变量、函数、表达式的类型
追踪返回值类型:auto和decltype相结合
auto func(int a,double b)->decltype(a+b)
{
return a+b;
}
在模板中使用更简洁
template<class T1,class T2>
auto mul(T1 &t1,T2 &t2)->->decltype(t1*t2)
{
return t1*t2;
}