右值引用深入场景分析
当右值引用想引用一个左值时,我们可以用move函数,将一个左值变为一个右值,但是要注意的是当一个左值调用move函数后,该左值的资源会被转移,所以不要轻易使用move。
完美转发
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 PerfectForward(T&& t)
{
Fun(t);
}
int main()
{
PerfectForward(10); // 右值
int a = 1;
PerfectForward(a); //左值
PerfectForward(move(a)); // 右值
const int b = 2;
PerfectForward(b); //const 左值
PerfectForward(move(b)); // const右值
return 0;
}
看一下运行结果
居然全是左值,这是怎么回事呢?
- 模板中的&&不代表右值引用,而是万能引用,既能接受左值也能接受右值
- 模板的万能引用只是提供了能够同时接收左值引用和右值引用的能力
- 但是引用类型的唯一作用就是限制了接收的类型,后续使用中都退化成了左值
- 如果希望保持它的左值或者右值属性,需要用到完美转发
完美转发
std::forward 在传参的过程中保留对象原生类型的属性
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 PerfectForward(T&& t)
{
Fun(forward<T>(t));
}
int main()
{
PerfectForward(10); // 右值
int a = 1;
PerfectForward(a); //左值
PerfectForward(move(a)); // 右值
const int b = 2;
PerfectForward(b); //const 左值
PerfectForward(move(b)); // const右值
return 0;
}
c++11 新增了移动构造函数和移动赋值运算符重载
注意:
- 如果你没有自己实现移动构造函数,且没有实现析构函数,拷贝构造,拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类型的成员会执行逐成员按字节拷贝,自定义类型成员会去掉它的构造函数,如果该自定义成员有移动构造就优先调用移动构造,不然调用拷贝构造。
- 移动赋值同上
- 如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。
c++新增关键字
default
c++11可以让你更好控制要使用的默认函数。假设你要使用某个默认的函数,但是因为一些原因这个函数没有默认生成。比如:我们提供了拷贝构造,就不会生成移动构造了,那么我们可以使用default关键字来显示指定移动构造的生成。
class Person
{
public:
Person(const char* name = " ")
:_name(name)
{}
Person(Person&& p) = default;
private:
string _name;
};
禁止生成默认函数的关键字delete
如果能想要限制某些默认函数的生成,在c++98中是该函数设置成private,并且只声明补丁,这样只要其他人想要调用就会报错。而在c++11中,只需在该函数声明加上 =delete即可,该语法让编译器不生成对应函数的默认版本。
class Person
{
public:
Person(const char* name = " ")
:_name(name)
{}
Person(const Person& p) = delete;
private:
string _name;
};
int main()
{
Person p;
Person p1 = p;
return 0;
}
会报错,无法调用拷贝构造,已经是删除了的函数。
可变参数模板
template<class ...Args>
void show(Args... x)
{
//Args是一个模板参数包,x是一个函数形参参数包
//声明一个参数包Args ...x 这个参数包中可以包含0到任意个模板参数。
}
上面的参数Args前面有省略号,所以它就是一个可变模板参数,我们把带省略号的参数称为“参数包”,它里面包含了0到N个模板参数。我们无法直接获得参数包args中的每一个参数,只能通过展开参数包的方式来获取参数包中的每个参数,这是使用可变模板参数的主要特点——如何展开可变模板参数。由于语法不支持args[i]这样的方式获取可变参数,我们用一些奇妙的小技巧来获得参数包的值。
递归函数方式展开参数包
template<class T>
void ShowList(const T& t)
{
cout << t << endl;
}
template<class T,class ...Args>
void ShowList(T value,Args... x)
{
cout << value << " ";
ShowList(x...);
}
逗号表达式展开参数包
template<class T>
void Print(T t)
{
cout << t << " ";
}
template<class ...Args>
void showlist(Args... x)
{
int arr[] = {(Print(x),0)...};
}