目录
[3.6 类型分类(了解)](#3.6 类型分类(了解))
[3.7 引用折叠(重点)](#3.7 引用折叠(重点))
[3.8 完美转发](#3.8 完美转发)
[5.1 基本语法及原理](#5.1 基本语法及原理)
[5.2 包扩展](#5.2 包扩展)
[5.3 emplace系列接口](#5.3 emplace系列接口)
[6. 新的类功能](#6. 新的类功能)
[6.1 默认的移动构造和移动赋值](#6.1 默认的移动构造和移动赋值)
[6.2 成员变量声明时给缺省值](#6.2 成员变量声明时给缺省值)
[6.4 final 与 override](#6.4 final 与 override)
[7. STL中一些变化](#7. STL中一些变化)
[8.1 function](#8.1 function)
[8.2 bind(绑定)](#8.2 bind(绑定))
对上一篇文章的一个小总结:
移动构造和移动赋值针对的是自定义类型,自定义那些类型又是那些临时对象,传值返回或是转换那些产生的临时对象或是匿名对象,这个时候就去转移资源,并且针对这些自定义类型,移动构造和右值引用这些针对的都是自定义里面深拷贝的类型,如:string,vector才会去谈论这些,若是日期类也是有移动构造这样的概念,但是跟拷贝构造是一样的动作。所以深拷贝的才会有移动构造、移动赋值,才会有右值引用的意义。
接上一篇文章的3.5小节
3.6 类型分类(了解)
- C++11以后,进一步对类型进行了划分,右值被划分纯右值(pure value,简称prvalue)和将亡值(expiring value,简称xvalue)。
- 纯右值 就是 那些字面值常量或 求值结果相当于字面值或是一个不具名的临时对象。如:11、true、nullptr或者类似 str1+str2 传值返回函数调用,或者整形a,b,a++ 等等。纯右值和将亡值是C++11中提出的,C++11中的纯右值概念划分等价于C++98中的右值。
- 将亡值是指返回右值引用的函数的调用表达式和转换为右值引用的转换函数的调用表达式,如move(x)。
- 泛左值(generalized value,简称glvalue),泛左值包含将亡值和左值。
关系如下图:

3.7 引用折叠(重点)
- C++中不能直接定义引用的引用如 int& && r = i;,这样写会直接报错,通过模板或typedef 中的类型操作可以构成引用的引用。
- 通过模板或typedef 中的类型操作可以构成引用的引用时,这时的C++11给出了一个引用折叠 的规则:右值引用的右值引用折叠成右值引用,其它所有的组合均折叠成左值引用。
- 下面的程序中很好的展示了模板和 typedef 时构成引用的引用时的引用折叠规则,大家需要一个一个仔细了解一下。
- 像f2这样的函数模板中,T&& x参数看起来是右值引用参数,但是由于引用折叠的规则,他传递左值是就是左值引用,传递右值时就是右值引用,有些地方也把这种函数模板的参数叫做万能引用。
- Function(T&& t)函数模板程序中,假设实参是int右值,模板参数T的推导 int,实参是int左值,模板参数T的推导 int& ,再结合引用折叠的规则,就实现了实参是左值,实例化出左值引用版本形参的Function,实参是右值,实例化出右值引用版本形参的Function。
cpp
typedef int& lref;
typedef int&& rref;
int main()
{
int n = 0;
lref& r1 = n; //r1类型是左值引用,int&
lref&& r2 = n; //r2类型是左值引用,int&
rref& r3 = n; //r3类型是左值引用,int&
rref&& r4 = 1; //r4类型是右值引用,int&&
return 0;
}
引用折叠其实在typedef这里用的都不多,而是在模板中用的多,函数模板中,我们就故意去实例化,将实参传递给形参,模板参数推演,但是也可以在函数模板那里显式实例化,显式实例化的话就跟参数没有什么关系了。此时就构成了一个引用折叠,代码如下:
引用折叠之后,一直都是左值引用:
cpp
typedef int& lref;
typedef int&& rref;
//函数模板,由于引用折叠的限定,f1实例化以后总是一个左值引用
template<class T>
void f1(T& x)
{ }
int main()
{
int n = 0;
lref& r1 = n; //r1类型是左值引用,int&
lref&& r2 = n; //r2类型是左值引用,int&
rref& r3 = n; //r3类型是左值引用,int&
rref&& r4 = 1; //r4类型是右值引用,int&&
//没有折叠->T实例化为int->实例化为void f1(int& x),int&只能传左值
f1<int>(n);
//f1<int>(0); //传右值,报错
//折叠->实例化为 void f1(int& x),左值引用,只能传左值
f1<int&>(n);
//f1<int&>(0); //传右值,报错
//折叠->实例化为 void f1(int& x),左值引用,只能传左值
f1<int&&>(n);
//f1<int&&>(0); //传右值,报错
//折叠->实例化为 void f1(const int& x),const左值引用,既可以传左值引用,又可以传右值引用
f1<const int&>(n);
f1<const int&>(0);
//折叠->实例化为 void f1(const int& x),const左值引用,既可以传左值引用,又可以传右值引用
f1<const int&&>(n); //右值引用,引用之后是可以修改的,因为表达式是左值属性,const修饰之后是不能修改的,所以这里是不能修改的
f1<const int&&>(0); //但是这里发生了引用折叠,所以是const 左值引用,所以传左值右值都行
return 0;
}
万能引用模板如下:
cpp
//由于引用折叠限定,f2实例化后可以是左值引用,也可以是右值引用
//万能引用
template<class T>
void f2(T&& x)
{ }
int main()
{
//没有折叠->实例化为void f2(int&& x)
//f2<int>(n); //传左值,报错
f2<int>(0);
//折叠->实例化为void f2(int& x)
f2<int&>(n);
//f2<int&>(0); //传右值,报错
//折叠->实例化为void f2(int&& x)
//f2<int&&>(n); //传左值,报错
f2<int&&>(0);
return 0;
}
上面为了推演用的是显示实例化的例子,实际中是不会用显示实例化的例子的,还是让模板参数自动推演,代码如下:
cpp
template<class T>
void Function(T&& t)
{
int a = 0;
T x = a;
//x++;
cout << &a << endl;
cout << &x << endl;
}
int main()
{
//10是右值,推导出T为int(相当于是一种规定),没有引用折叠,模板参数实例化为 void Function(int&& t)
Function(10);
return 0;
}
上面的10是右值,推演出来就是右值引用,但是10传过去的时候T有可能是 int ,有可能是 int &&,这样的话两种情况都能折叠为右值引用,那么倒推回来的话,T到底是什么类型呢?以下内容做解释:
T如果是int 右值引用,以下代码是编不过的:
cpp
T x = a;
T 是 int 右值引用的话,x是不能引用 a 的。

但是此时的代码在编译器上是能编过的,并且a和x的地址是不一样的,那就说明 T 是 int,a拷贝赋值给给 x。
再看第二组:
cpp
template<class T>
void Function(T&& t)
{
int a = 0;
T x = a;
x++;
cout << &a << endl;
cout << &x << endl;
}
int main()
{
//10是右值,推导出T为int(相当于是一种规定),没有引用折叠,模板参数实例化为 void Function(int&& t)
Function(10);
int a;
//a 是左值,推导出T为int&,引用折叠,模板实例化为void Function(int& t)
Function(a); //是可以传左值的
return 0;
}
传左值就能实例化出左值引用的版本,传右值就能实例化出右值引用的版本。第二组中如何证明T是int&?
cpp
T x = a;
如果T是 int& 的话,意味着x和a的地址是一样的,并且x++,a也++了,结果如下:

再看第三组:
cpp
int main()
{
//std::move(a)是右值(严格来说move(左值)是一个将亡值,也属于右值),推导出T为int,模板实例化为void Function(int&& t)------不折叠
Function(std::move(a)); //传的是将亡值,也属于右值
//验证方法:
//a和x地址不一样,表明T就是int类型
return 0;
}
运行结果:

第三组本质是和第一组归为一类的,只是第一组传的是纯右值,第三组传的是将亡值(但是将亡值也属于右值)
第四组:
cpp
template<class T>
void Function(T&& t)
{
int a = 0;
T x = a;
//x++; //x": 不能给常量赋值
cout << &a << endl;
cout << &x << endl << endl;
}
int main()
{
const int b = 8;
//b是左值,推导出T为const int&,引用折叠,模板实例化为void Function(const int &)
Function(b); //推导出来就是左值引用,但是是const左值引用,有const修饰,所以不能++,否则报错->"x": 不能给常量赋值
//此时x就是a的别名,所以两者的地址是一样的
return 0;
}
推出来T为const int&,所以x是a的别名,此时二者指向的是同一块地址:

第五组:
cpp
template<class T>
void Function(T&& t)
{
int a = 0;
T x = a;
//x++;
cout << &a << endl;
cout << &x << endl << endl;
}
int main()
{
//......
//std::move(b)对const 左值进行move,move之后就变为const右值,推出T类型为const int,没有折叠,模板实例化为void Function(const int&&t)
//看a和x的地址不一样T类型便为int,反之则为int&&;但是这里不能对x++ ->"x": 不能给常量赋值;
Function(std::move(b)); //右值
return 0;
}
运行结果:

总结:
上面的几组代码验证了以下代码就是万能引用,传普通左值就是普通的左值引用,传const左值就是const左值引用;传普通的右值就是普通的右值引用,传const右值就是const右值引用
template<class T>
void Function(T&& t)
{
//......
}
综上所述:
上面代码的所有情况都是围绕这句话展开的:
右值引用的右值引用折叠成右值引用,其它所有的组合均折叠成左值引用。
但是引用折叠的出现又会引发一些新的问题:
之前我们在list.h中改的代码如下,下面的代码也是万能引用吗?
cpp
//不是万能引用
//普通的右值引用
void push_back(T&& x)
{
insert(end(), move(x));
}
上面的代码并不是万能引用!!!就是一个普通的右值引用!!!
T的类型不是由实参决定的,T不是 push_back()的模板参数,T是类模板的,T是在类模板实例化的时候决定的,类模板实例化就把函数实例化了,这里并没有引用折叠这些概念,这里的T就是 int,也没有什么折叠,T是int,push_back()就是右值引用

cpp
//void push_back(const T& x)
//{
// insert(end(), x);
//}
////不是万能引用
////普通的右值引用
//void push_back(T&& x)
//{
// insert(end(), move(x));
//}
下面的一个模板替代的就是上面的两个函数
cpp
//将push_back和insert进行合并
//是万能引用
//成员函数也可以是函数模板
template<class X>
void push_back(X&& x)
{
insert(end(), move(x));
}
此模板就可以实现传左值过来,也可以传右值过来,但是这里有一处代码不合适:
cpp
insert(end(), move(x));
全部move()的话,不管x为左值还是右值,就全部转为右值了,所以就要保持x的属性,保持x的属性就要引出完美转发的概念。
3.8 完美转发
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);
}
int main()
{
//10是右值,推导出T为int,模板实例化为void Function(int&& t)
Function(10); //右值
int a; //左值
//a为左值,推导出T为int&,引用折叠,模板实例化为void Function(int& t)
Function(a); //左值
//std::move(a)为右值,推导出T为int,模板实例化为void Function(int&& t)
Function(std::move(a));//右值
//b是const左值,推导出T为const int&,引用折叠,模板实例化为void Function(const int& t)
const int b = 8;
Function(b); //const 左值
//std::move(b)是右值,推导出来T为 const int,模板实例化为void Function(const int&& t)
Function(std::move(b)); //const 右值
return 0;
}
运行结果:

但是为什么全部匹配到左值引用那块去了呢?
无论Function()被实例化成左值引用版本还是右值引用版本,t都是引用的变量表达式,变量表式的属性都是左值,都会匹配成左值引用或者是const 左值引用,此时那我们move()一下:
cpp
Fun(move(t));
move()了之后又全部变成右值了,右值还是右值,左值还是被move()成了右值。

这并不是我们想要达到的效果,我们希望保持原有的属性。这样才能传给下一层的insert(),insert()才能传给下一层对应的构造,此时就引出了完美转发:forward
cpp
Fun(forward<T>(t));
forward的特点是:保持它原有的属性,如果被实例化成右值,保持右值属性,如果已经是右值的话,但是变量表达式属于左值,底层就会强转一下,相当于move()一下 ,才能保持右值属性;如果是左值就不强转,保持你的左值属性。以下是一些具体的例子来理解本段话的意思:
- 当 T 被推导为 int(非引用)时,forward<T>(t) 返回 int&&(右值)。
- Function(10) 中,参数 t 本身是右值引用类型的变量 int&& t,但 t 作为表达式是左值,所以需要 forward 把它"变回"右值。forward 内部执行 static_cast<int&&>(t),确实相当于 move(t)。
- 当 T 是 int& 时,forward 返回 int&,不做任何转换。

完美转发forward本质是一个函数模板,是由多层函数模板做到的
cpp
template <class _Ty>
_Ty&& forward(remove_reference_t<_Ty>& _Arg) noexcept
{ // forward an lvalue as either an lvalue or an rvalue
return static_cast<_Ty&&>(_Arg);
}
这个函数模板这样写:
cpp
Fun(forward<T>(t));
显示实例化,底层就要去识别他是左值还是右值,<T>识别出是有右值引用,t为表达式属于左值属性,此时就要强转一下,保持原有的右值属性;如果是左值就不用动,这里如何区分你是左值还是右值呢?(简单的了解一下)




接上一篇文章的4小节之后的内容
5.可变参数模板
5.1 基本语法及原理
之前在C语言的printf、scanf中见过可变模板参数,可以同时printf,scanf多个值,可变参数的原理是printf通过传过来的实参,底层会开一个二维数组去存,多个参数都会帮你存起来。
在C++中需要的是模板的可变参数,需要多个模板参数,多个模板参数是什么类型呢?不确定。想要什么类型就是什么类型。就引出了一个模板参数包的概念,模板参数包:表示零或多个模板参数;函数参数包:表示零或多个函数参数。
- template <class ...Args> void Func(Args... args) {}
//无参的Func, <class ...Args>模板参数包, Func(Args... args)函数参数包,传的是什么类型推出的就是什么类型
- template <class ...Args> void Func(Args&... args) {}
//左值引用
- template <class ...Args> void Func(Args&&... args) {} (常用)
//万能引用
cpp
//可以传任意多个实参,并且实参的类型是不一样的,可自动推
//替代的是3个模板和一个普通函数
//一个无参的,一个模板参数,一个两个模板参数,一个三个模板参数
template<class ...Args>
void Print(Args&&... args)
{
cout << sizeof...(args) << endl; //sizeof...新的运算符,计算参数包有多少个参数
}
int main()
{
double x = 2.2;
Print();//包里有0个参数
Print(1);//包里有1个参数
Print(1,string("ysy"));//包里有0个参数
Print(4.12,string("ysy"),x);//包里有0个参数
return 0;
}

cpp
//可以传任意多个实参,并且实参的类型是不一样的,可自动推
//替代的是3个模板和一个普通函数
//一个无参的,一个模板参数,一个两个模板参数,一个三个模板参数
//template<class ...Args>
//void Print(Args&&... args)
//{
// cout << sizeof...(args) << endl; //sizeof...新的运算符,计算参数包有多少个参数
// ////不支持这样的写法,因为本身就是没有这样的语法的
// ////但是C++17好像有这样的支持的方法,但是也要考虑效率的问题
// //for (size_t i = 0; i < sizeof...(args; i++)
// //{
// // cout << args[i] << " ";
// // //args被当成容器去用,但是一个容器是不支持既存int,又存double类型的,
// // //一个容器是不支持存不同类型的,所以就不能被访问,所以就不能这样写
// //}
// //cout << endl;
//
//}
//原理1:编译本质这里会结合引用折叠规则数理化出以下四个函数
//void Print();
//void Print(int&& arg1);
//void Print(int&& arg1, string&& arg2);
//void Print(double&& arg1, string&& arg2, double& arg3);
// 原理2:更本质去看没有可变参数模板:template<class ...Args>,我们实现出这样的多个函数
// 这里的功能,有了可变参数模板,我们进⼀步被解放,他是类型泛化基础
// 上叠加数量变化,让我们泛型编程更灵活。
void Print(); //无参
template <class T1> //1个参数
void Print(T1&& arg1);
template <class T1, class T2>//2个参数
void Print(T1&& arg1, T2&& arg2);
template <class T1, class T2, class T3> //3个参数
void Print(T1&& arg1, T2&& arg2, T3&& arg3);
int main()
{
double x = 2.2;
Print();//包里有0个参数
Print(1);//包里有1个参数
Print(1,string("ysy"));//包里有0个参数
Print(4.12,string("ysy"),x);//包里有0个参数
return 0;
}

总结:
模板的可变参数的逻辑,可变参数的模板是更进一步的泛型,是在泛型的基础上再叠加泛型,叠加数量上的泛型,上图中的下面的方法是类型的泛型,上图中的上面的方法是类型是泛型,个数也是不限参数个数的泛型。
想把参数包里面的内容解析出来,应该怎样写呢?里面应该是还是有东西的,还是需要取出里面的所有参数,如何实现呢?------ 包扩展(递归扩展)
5.2 包扩展
cpp
void ShowList()
{
//编译器时递归的终止条件,参数包是0个时,直接匹配这个函数
cout << endl;
}
template <class T,class ...Args>
void ShowList(T x, Args...args)
{
cout << x << "";
//args是N个参数的参数包
//调用ShowList,参数包的第一个传给x,剩下N-1个传给第二个参数包
ShowList(args...);
}
//编译时递归推导解析参数
template <class ...Args>
void Print(Args... args)
{
ShowList(args...);
}
int main()
{
double x = 2.2;
Print();//包里有0个参数
Print(1);//包里有1个参数
Print(1,string("ysy"));//包里有2个参数
Print(4.12,string("ysy"),x);//包里有3个参数
return 0;
}
解析:
- void Print(Args... args) 中的 Print(Args... args),代表0到N个参数。如果传的是0个参数,参数包args...代表0个参数,直接匹配的就是:void ShowList() { cout << endl; } 打印个换行,啥都没有。
- 如果是1个参数呢?Print(1);//包里有1个参数,一个参数的参数包传给 ShowList(T x, Args...args)
- { cout << x << " "; ShowList(args...) },ShowList第一个形参是int,推出为int,剩下的参数包传给Args...args,只有一个参数包,剩下的参数包为0,打印 int:cout << x << " ",之后调用ShowList(args...),参数为0,直接打印换行,结束。
- 如果2个参数呢? Print(1,string("ysy"));//包里有2个参数,第一个参数是int,推出为int,int打印了,还剩一个string的参数包再来调ShowList,这个时候就是调自己,这个时候就是编译时的递归,再对这个模板进行推演,再推出一个string的和0个参数的参数包,再打印string,0个参数包再往后走,匹配 void ShowList() 就结束了。注意:这里的递归是编译时的递归,并不是在运行的时候。
- 3个参数的也是同样的道理。
能不能写这样的结束条件呢?------ 不能,因为是编译的时候在这推导,编译时推导就是要推导出一系列的函数。
cpp
template <class T,class ...Args>
void ShowList(T x, Args...args)
{
/*if (sizeof...(args) == 0)
return;*/
//不能这样写,因为这样是运行时的逻辑
cout << x << "";
ShowList(args...);
}
简单的来说,左边的代码本质就是要生成右边的一堆代码:

右边的虽然说是递归马,但是严格来说并不是自己调自己,而是去调用了对应的重载版本,运行时递归是自己调自己,编译时递归,本质上是用一个函数去推出另一个重载函数。
还有一种方式去解析包里面的内容:
cpp
template <class T>
const T& GetArg(const T& x)
{
cout << x << " ";
return x;
}
template <class ...Args>
void Arguments(Args... args)
{ }
template <class ...Args>
void Print(Args... args)
{
//注意GetArg必须返回或者到的对象,这样才能组成参数包给Arguments
//参数包每个参数传给GetArg处理,处理以后返回值作为实参组合参数包传递给Arguments函数
Arguments(GetArg(args)...);
cout << endl;
}
int main()
{
double x = 2.2;
Print();//包里有0个参数
Print(1);//包里有1个参数
Print(1,string("ysy"));//包里有2个参数
Print(4,string("ysy"),2.2);//包里有3个参数
return 0;
}
运行结果:


比如说:Print(4,string("ysy"),2.2);,类型是int ,string,double,要调用Arguments()先调用GetArg(args),用GetArg(args)的返回值组成参数包,再传给下一层,本质上就是一个实参传形参,但是参数包里面的每个数据都要给GetArg()处理,通过这个模板const T& GetArg(const T& x)解析出了参数包里面的参数,然后进行打印。类型是int ,string,double时推演的时候会推演成这样:
// 本质可以理解为编译器编译时,包的扩展模式
// 将上⾯的函数模板扩展实例化为下⾯的函数
//void Print(int x, string y, double z)
//{
// Arguments(GetArg(x), GetArg(y), GetArg(z));
//}
这样话除了template <class T>
const T& GetArg(const T& x)
{
cout << x << " ";
return x;
}
这个是实际上处理参数包的数据就在这,其余的都是空壳。
cpp
template <class T>
int GetArg(const T& x)
{
cout << x << " ";
return 0;
}
注意这里的返回类型是什么已经不重要了,因为我们的目的(打印)已经达成,但是必须要有返回值,因为返回值要作为Arguments的实参。
5.3 emplace系列接口
push_back有左值引用版本,有右值引用版本,因为value_type是在类模板的时候实例化的,不是函数模板不是推演的,为什么还要有emplace呢?

emplace_banck是函数模板,是万能引用并且叠加了可变模板参数包

C++11之后所有的STL容器都新增了emplace系列的接口,有push_back 就是emplace_back,有push就是emplace,有insert就是emplace。
emplace_back走题而言是更高效的,推荐之后使用emplace系列代替insert和push系列。
emplace的使用:
emplace_back和emplace替代insert和push系列都一样,有一部分的功能跟push_back系列的功能是重叠的。
cpp
int main()
{
list<ysy::string> lt;
//传左值,跟push_back一样,走拷贝构造
ysy::string s1("ysy");
lt.emplace_back(s1);
cout << "**************************************************************" << endl;
lt.push_back(s1);
return 0;
}
std::list::push_back:确定的一个T类型的参数
std::list::emplace_back:是0到N个参数的参数包,传左值就是左值引用,传右值就是右值引用,const左值就是const左值引用,const右值就是const右值引用,由于之前的引用折叠达到的万能引用的特性。
运行结果:

std::list::push_back和std::list::emplace_back都是走一次拷贝构造,因为都是左值,左值不敢移动你的资源。
传递的是右值呢?右值的话实例化出一个右值引用的版本,右值实例化出string,string没有折叠,就是右值引用,走移动构造:
cpp
int main()
{
list<ysy::string> lt;
//传左值,跟push_back一样,走拷贝构造
ysy::string s1("ysy");
lt.emplace_back(s1);
//lt.push_back(s1);
cout << "**************************************************************" << endl;
//右值,跟push_back一样,走移动构造
lt.emplace_back(move(s1));
cout << "**************************************************************" << endl;
return 0;
}

换成 lt.push_back(move(s1)); :
cpp
int main()
{
list<ysy::string> lt;
//传左值,跟push_back一样,走拷贝构造
ysy::string s1("ysy");
lt.emplace_back(s1);
//lt.push_back(s1);
cout << "**************************************************************" << endl;
//右值,跟push_back一样,走移动构造
//lt.emplace_back(move(s1));
lt.push_back(move(s1));
cout << "**************************************************************" << endl;
return 0;
}
运行结果:

所以说:emplace_back 和 push_back有一部分的功能是重叠的。
下面就是二者不一样的部分了:
emplace_back是可变参数模板,是可以传const char*,
cpp
int main()
{
list<ysy::string> lt;
//传左值,跟push_back一样,走拷贝构造
ysy::string s1("ysy");
lt.emplace_back(s1);
//lt.push_back(s1);
cout << "**************************************************************" << endl;
//右值,跟push_back一样,走移动构造
//lt.emplace_back(move(s1));
lt.push_back(move(s1));
cout << "**************************************************************" << endl;
cout << "***********************下面就是二者不一样的部分了*************" << endl;
//直接把构造string参数包往下传,直接用string参数包构造string
//这里达到的效果是push_back做不到的
lt.emplace_back("sysysy");
cout << "**************************************************************" << endl;
return 0;
}
运行结果:

push_back:
cpp
int main()
{
list<ysy::string> lt;
//传左值,跟push_back一样,走拷贝构造
ysy::string s1("ysy");
lt.emplace_back(s1);
//lt.push_back(s1);
cout << "**************************************************************" << endl;
//右值,跟push_back一样,走移动构造
//lt.emplace_back(move(s1));
lt.push_back(move(s1));
cout << "**************************************************************" << endl;
cout << "***********************下面就是二者不一样的部分了*************" << endl;
//直接把构造string参数包往下传,直接用string参数包构造string
//这里达到的效果是push_back做不到的
lt.emplace_back("sysysy");
lt.push_back("sysysy");
cout << "**************************************************************" << endl;
return 0;
}
运行结果:

emplace_back是构造,而push_back是构造+移动构造。emplace这个系列就会高效一些,他是如何做到直接是构造这个概念的呢?

之前的举的例子都是单参数的,用法一致,单参数string看可以传左值,可以传右值,可以传构造string的不同的是一个是直接构造,层层往下传,一个是类型转换构造+移动构造的,效率差别也不是特别大,这是针对深拷贝的类型,差别没有那么大,如果是浅拷贝的类型,差别反而更大一些。
多参数的:
cpp
int main()
{
list<ysy::string> lt;
//传左值,跟push_back一样,走拷贝构造
ysy::string s1("ysy");
lt.emplace_back(s1);
//lt.push_back(s1);
cout << "**************************************************************" << endl;
//右值,跟push_back一样,走移动构造
//lt.emplace_back(move(s1));
lt.push_back(move(s1));
cout << "**************************************************************" << endl;
cout << "***********************下面就是二者不一样的部分了*************" << endl;
//直接把构造string参数包往下传,直接用string参数包构造string
//这里达到的效果是push_back做不到的
lt.emplace_back("sysysy");
lt.push_back("sysysy");
cout << "**************************************************************" << endl;
list<pair<ysy::string, int>> lt1;
//跟push_back一样
//构造pair+拷贝/移动构造pair到list的结点中data上
pair<ysy::string, int> kv("苹果", 1);
lt1.emplace_back(kv);
cout << "**************************************************************" << endl;
return 0;
}
运行结果:(因为是左值,构造+拷贝构造)


右值:

运行结果:


运行结果:(不能move(kv)),因为资源已将转走了

运行结果:

多参数功能不同的部分:、
cpp
int main()
{
lt1.emplace_back("草莓",1);
//lt1.emplace_back({ "草莓",1 }); //emplace_back不能这样写
//lt1.push_back("草莓",1); //push_back不能这样写
lt1.push_back({ "草莓",1 });
cout << "**************************************************************" << endl;
return 0;
}
运行结果:


总体的一个结论:
emplace_back总体而言是更高效的,推荐以后使用emplace系列代替insert和push系列
只显示了有关实现emplace的关键代码:
cpp
#pragma once
#include<assert.h>
using namespace std;
namespace ysy
{
template<class T>
struct list_node
{
list_node<T>* _next;
list_node<T>* _prev;
T _data;
list_node(const T& x = T())
:_next(nullptr)
, _prev(nullptr)
, _data(x)
{
}
list_node(T&& x)
:_next(nullptr)
, _prev(nullptr)
, _data(move(x))
{
}
//构造也得改
template <class... Args>
list_node(Args&&... args)
:_next(nullptr)
, _prev(nullptr)
, _data(forward<Args>(args)...)
{}
};
//......
template<class T>
class list
{
//......
template <class... Args>
void emplace_back(Args&&... args)
{
emplace(end(), forward<Args>(args)...);
}
template <class... Args>
void emplace(iterator pos, Args&&... args)
{
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* newnode = new Node(forward<Args>(args)...);
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = cur;
cur->_prev = newnode;
++_size;
}
//......
}
private:
Node* _head;
size_t _size;
};
}
换为自己实现的类:
cpp
int main()
{
ysy::list<ysy::string> lt;
//传左值,跟push_back一样,走拷贝构造
ysy::string s1("ysy");
lt.emplace_back(s1);
//lt.push_back(s1);
cout << "**************************************************************" << endl;
//右值,跟push_back一样,走移动构造
//lt.emplace_back(move(s1));
lt.push_back(move(s1));
cout << "**************************************************************" << endl;
cout << "***********************下面就是二者不一样的部分了*************" << endl;
//直接把构造string参数包往下传,直接用string参数包构造string
//这里达到的效果是push_back做不到的
lt.emplace_back("sysysy");
lt.push_back("sysysy");
cout << "**************************************************************" << endl;
cout << "* **********************下面就是二者功能重叠的部分 * ************" << endl;
list<pair<ysy::string, int>> lt1;
//跟push_back一样
//构造pair+拷贝/移动构造pair到list的结点中data上
pair<ysy::string, int> kv("苹果", 1);
lt1.emplace_back(kv);
lt1.push_back(kv);
cout << "**************************************************************" << endl;
//跟push_back一样
//lt1.emplace_back(move(kv));
lt1.push_back(move(kv));
cout << "**************************************************************" << endl;
cout << "***********************下面就是二者不一样的部分了*************" << endl;
lt1.emplace_back("草莓",1);
//lt1.emplace_back({ "草莓",1 }); //emplace_back不能这样写,加上{}类型不明确,"草莓",1类型不一样
//lt1.push_back("草莓",1); //push_back不能这样写
lt1.push_back({ "草莓",1 });
cout << "**************************************************************" << endl;
return 0;
}
运行结果和刚刚使用库里面的一样:(除了自己实现的有哨兵位的头节点的构造,其余的都一样)

完整代码:
cpp
//list.h
#pragma once
#include<assert.h>
using namespace std;
namespace ysy
{
template<class T>
struct list_node
{
list_node<T>* _next;
list_node<T>* _prev;
T _data;
list_node(const T& x = T())
:_next(nullptr)
, _prev(nullptr)
, _data(x)
{
}
list_node(T&& x)
:_next(nullptr)
, _prev(nullptr)
, _data(move(x))
{
}
//构造也得改
template <class... Args>
list_node(Args&&... args)
:_next(nullptr)
, _prev(nullptr)
, _data(forward<Args>(args)...)
{}
};
template<class T, class Ref, class Ptr>
struct list_iterator
{
typedef list_node<T> Node;
typedef list_iterator<T, Ref, Ptr> Self;
Node* _node;
list_iterator(Node* node)
:_node(node)
{
}
Ref operator*()
{
return _node->_data;
}
Ptr operator->()
{
return &_node->_data;
}
Self& operator++()
{
_node = _node->_next;
return *this;
}
Self operator++(int)
{
Self tmp(*this);
_node = _node->_next;
return tmp;
}
Self& operator--()
{
_node = _node->_prev;
return *this;
}
Self operator--(int)
{
Self tmp(*this);
_node = _node->_prev;
return tmp;
}
bool operator!=(const Self& it)
{
return _node != it._node;
}
bool operator==(const Self& it)
{
return _node == it._node;
}
};
/*template<class T>
struct list_iterator
{
typedef list_node<T> Node;
Node* _node;
list_iterator(Node* node)
:_node(node)
{}
T& operator*()
{
return _node->_data;
}
T* operator->()
{
return &_node->_data;
}
list_iterator<T>& operator++()
{
_node = _node->_next;
return *this;
}
bool operator!=(const list_iterator<T>& it)
{
return _node != it._node;
}
bool operator==(const list_iterator<T>& it)
{
return _node == it._node;
}
};
template<class T>
struct list_const_iterator
{
typedef list_node<T> Node;
Node* _node;
list_const_iterator(Node* node)
:_node(node)
{}
const T& operator*()
{
return _node->_data;
}
const T* operator->()
{
return &_node->_data;
}
list_const_iterator<T>& operator++()
{
_node = _node->_next;
return *this;
}
bool operator!=(const list_const_iterator<T>& it)
{
return _node != it._node;
}
bool operator==(const list_const_iterator<T>& it)
{
return _node == it._node;
}
};*/
template<class T>
class list
{
typedef list_node<T> Node;
public:
//typedef Node* iterator;
/* typedef list_iterator<T> iterator;
typedef list_const_iterator<T> const_iterator;*/
typedef list_iterator<T, T&, T*> iterator;
typedef list_iterator<T, const T&, const T*> const_iterator;
iterator begin()
{
//return iterator(_head->_next);
iterator it(_head->_next);
return it;
}
iterator end()
{
return iterator(_head);
}
const_iterator begin() const
{
return const_iterator(_head->_next);
}
const_iterator end() const
{
return const_iterator(_head);
}
void empty_init()
{
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
_size = 0;
}
list()
{
empty_init();
}
list(initializer_list<T> lt)
{
empty_init();
for (auto& e : lt)
{
push_back(e);
}
}
// lt2(lt1)
list(const list<T>& lt)
{
empty_init();
for (auto& e : lt)
{
push_back(e);
}
}
// lt1 = lt3
list<T>& operator=(list<T> lt)
{
swap(lt);
return *this;
}
void swap(list<T>& lt)
{
std::swap(_head, lt._head);
std::swap(_size, lt._size);
}
~list()
{
clear();
delete _head;
_head = nullptr;
}
void clear()
{
iterator it = begin();
while (it != end())
{
it = erase(it);
}
}
size_t size() const
{
return _size;
}
void push_back(const T& x)
{
insert(end(), x);
}
//不是万能引用
//普通的右值引用
void push_back(T&& x)
{
insert(end(), move(x));
}
//将push_back和insert进行合并
//是万能引用
//成员函数也可以是函数模板
/*template<class X>
void push_back(X&& x)
{
insert(end(), move(x));
}*/
void push_front(const T& x)
{
insert(begin(), x);
}
void pop_front()
{
erase(begin());
}
void pop_back()
{
erase(--end());
}
template <class... Args>
void emplace_back(Args&&... args)
{
emplace(end(), forward<Args>(args)...);
}
template <class... Args>
void emplace(iterator pos, Args&&... args)
{
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* newnode = new Node(forward<Args>(args)...);
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = cur;
cur->_prev = newnode;
++_size;
}
void insert(iterator pos, const T& x)
{
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* newnode = new Node(x);
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = cur;
cur->_prev = newnode;
++_size;
}
void insert(iterator pos, T&& x)
{
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* newnode = new Node(move(x));
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = cur;
cur->_prev = newnode;
++_size;
}
iterator erase(iterator pos)
{
assert(pos != end());
Node* cur = pos._node;
Node* nextNode = cur->_next;
Node* prevNode = cur->_prev;
prevNode->_next = nextNode;
nextNode->_prev = prevNode;
delete cur;
--_size;
return iterator(nextNode);
}
private:
Node* _head;
size_t _size;
};
}
//Test.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<algorithm>
#include<map>
#include<string>
#include<list>
#include<vector>
#include<assert.h>
#include"list.h"
namespace ysy
{
class string
{
public:
typedef char* iterator;
typedef const char* const_iterator;
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& tmp)
{
::swap(_str, tmp._str);
::swap(_size, tmp._size);
::swap(_capacity, tmp._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()
{
if (_str)
{
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;
};
}
typedef int& lref;
typedef int&& rref;
//函数模板,由于引用折叠的限定,f1实例化以后总是一个左值引用
template<class T>
void f1(T& x)
{ }
//由于引用折叠限定,f2实例化后可以是左值引用,也可以是右值引用
//万能引用
template<class T>
void f2(T&& x)
{ }
//int main()
//{
// int n = 0;
// lref& r1 = n; //r1类型是左值引用,int&
// lref&& r2 = n; //r2类型是左值引用,int&
// rref& r3 = n; //r3类型是左值引用,int&
// rref&& r4 = 1; //r4类型是右值引用,int&&
//
// //没有折叠->T实例化为int->实例化为void f1(int& x),int&只能传左值
// f1<int>(n);
// //f1<int>(0); //传右值,报错
//
// //折叠->实例化为 void f1(int& x),左值引用,只能传左值
// f1<int&>(n);
// //f1<int&>(0); //传右值,报错
//
// //折叠->实例化为 void f1(int& x),左值引用,只能传左值
// f1<int&&>(n);
// //f1<int&&>(0); //传右值,报错
//
// //折叠->实例化为 void f1(const int& x),const左值引用,既可以传左值引用,又可以传右值引用
// f1<const int&>(n);
// f1<const int&>(0);
//
// //折叠->实例化为 void f1(const int& x),const左值引用,既可以传左值引用,又可以传右值引用
// f1<const int&&>(n); //右值引用,引用之后是可以修改的,因为表达式是左值属性,const修饰之后是不能修改的,所以这里是不能修改的
// f1<const int&&>(0); //但是这里发生了引用折叠,所以是const 左值引用,所以传左值右值都行
//
// //没有折叠->实例化为void f2(int&& x)
// //f2<int>(n); //传左值,报错
// f2<int>(0);
//
// //折叠->实例化为void f2(int& x)
// f2<int&>(n);
// //f2<int&>(0); //传右值,报错
//
// //折叠->实例化为void f2(int&& x)
// //f2<int&&>(n); //传左值,报错
// f2<int&&>(0);
//
// return 0;
//}
////万能引用
//template<class T>
//void Function(T&& t)
//{
// int a = 0;
// T x = a;
// //x++;
// cout << &a << endl;
// cout << &x << endl << endl;
//}
//
//int main()
//{
// //10是右值,推导出T为int(相当于是一种规定),没有引用折叠,模板参数实例化为 void Function(int&& t) ------不折叠
// Function(10); //传的是纯右值
// //验证方法:
// //a和x地址不一样,表明T就是int类型
//
// int a;
// //a 是左值,推导出T为int&,引用折叠,模板实例化为void Function(int& t)
// Function(a); //是可以传左值的
// //验证方法:
// //a和x地址一样,表明T就是int&类型
//
// //std::move(a)是右值(严格来说move(左值)是一个将亡值,也属于右值),推导出T为int,模板实例化为void Function(int&& t)------不折叠
// Function(std::move(a)); //传的是将亡值,也属于右值
// //验证方法:
// //a和x地址不一样,表明T就是int类型
//
// const int b = 8;
// //b是左值,推导出T为const int&,引用折叠,模板实例化为void Function(const int &)
// Function(b); //推导出来就是左值引用,但是是const左值引用,有const修饰,所以不能++,否则报错->"x": 不能给常量赋值
// //此时x就是a的别名,所以两者的地址是一样的
//
// //std::move(b)对const 左值进行move,move之后就变为const右值,推出T类型为const int,没有折叠,模板实例化为void Function(const int&&t)
// //看a和x的地址不一样T类型便为int,反之则为int&&;但是这里不能对x++ ->"x": 不能给常量赋值;
// Function(std::move(b)); //右值
//
// return 0;
//}
//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(forward<T>(t));
//}
//
//int main()
//{
// //10是右值,推导出T为int,模板实例化为void Function(int&& t)
// Function(10); //右值
//
// int a; //左值
// //a为左值,推导出T为int&,引用折叠,模板实例化为void Function(int& t)
// Function(a); //左值
//
// //std::move(a)为右值,推导出T为int,模板实例化为void Function(int&& t)
// Function(std::move(a));//右值
//
// //b是const左值,推导出T为const int&,引用折叠,模板实例化为void Function(const int& t)
// const int b = 8;
// Function(b); //const 左值
//
// //std::move(b)是右值,推导出来T为 const int,模板实例化为void Function(const int&& t)
// Function(std::move(b)); //const 右值
// return 0;
//}
//可以传任意多个实参,并且实参的类型是不一样的,可自动推
//替代的是3个模板和一个普通函数
//一个无参的,一个模板参数,一个两个模板参数,一个三个模板参数
//template<class ...Args>
//void Print(Args&&... args)
//{
// cout << sizeof...(args) << endl; //sizeof...新的运算符,计算参数包有多少个参数
// ////不支持这样的写法,因为本身就是没有这样的语法的
// ////但是C++17好像有这样的支持的方法,但是也要考虑效率的问题
// //for (size_t i = 0; i < sizeof...(args; i++)
// //{
// // cout << args[i] << " ";
// // //args被当成容器去用,但是一个容器是不支持既存int,又存double类型的,
// // //一个容器是不支持存不同类型的,所以就不能被访问,所以就不能这样写
// //}
// //cout << endl;
//
//}
//原理1:编译本质这里会结合引用折叠规则数理化出以下四个函数
//void Print();
//void Print(int&& arg1);
//void Print(int&& arg1, string&& arg2);
//void Print(double&& arg1, string&& arg2, double& arg3);
// 原理2:更本质去看没有可变参数模板:template<class ...Args>,我们实现出这样的多个函数
// 这里的功能,有了可变参数模板,我们进?步被解放,他是类型泛化基础
// 上叠加数量变化,让我们泛型编程更灵活。
//void Print(); //无参
//template <class T1> //1个参数
//void Print(T1&& arg1);
//template <class T1, class T2>//2个参数
//void Print(T1&& arg1, T2&& arg2);
//template <class T1, class T2, class T3> //3个参数
//void Print(T1&& arg1, T2&& arg2, T3&& arg3);
//void ShowList()
//{
// //编译器时递归的终止条件,参数包是0个时,直接匹配这个函数
// cout << endl;
//}
//
//template <class T,class ...Args>
//void ShowList(T x, Args...args)
//{
// /*if (sizeof...(args) == 0)
// return;*/
// //不能这样写,因为这样是运行时的逻辑
//
// cout << x << "";
// //args是N个参数的参数包
// //调用ShowList,参数包的第一个传给x,剩下N-1个传给第二个参数包
// ShowList(args...);
//}
//
////编译时递归推导解析参数
//template <class ...Args>
//void Print(Args... args)
//{
// //args是N个参数的参数包
// //调用ShowList,参数包的第一个传给x
// //剩下N-1传给第二个参数包
// ShowList(args...);
//}
////本质编译器将可变参数模板通过模式的包扩展,
////编译器推导的以下三个函数
//void ShowList(double x)
//{
// cout << x << " ";
// ShowList();
//}
//
//void ShowList(string x, double z)
//{
// cout << x << " ";
// ShowList(z);
//
//}
//
//void ShowList(int x, string y, double z)
//{
// cout << x << " ";
// ShowList(y, z);
//}
//
//void Print(int x, string y, double z)
//{
// ShowList(x, y, z);
//}
//template <class T>
//const T& GetArg(const T& x)
//{
// cout << x << " ";
// return x;
//}
//
//template <class ...Args>
//void Arguments(Args... args)
//{ }
//
//template <class ...Args>
//void Print(Args... args)
//{
// //注意GetArg必须返回或者到的对象,这样才能组成参数包给Arguments
// //参数包每个参数传给GetArg处理,处理以后返回值作为实参组合参数包传递给Arguments函数
// Arguments(GetArg(args)...);
// cout << endl;
//}
//
//// 本质可以理解为编译器编译时,包的扩展模式
//// 将上面的函数模板扩展实例化为下面的函数
////void Print(int x, string y, double z)
////{
//// Arguments(GetArg(x), GetArg(y), GetArg(z));
////}
//
//int main()
//{
// double x = 2.2;
// Print();//包里有0个参数
// Print(1);//包里有1个参数
// Print(1,string("ysy"));//包里有2个参数
// Print(4,string("ysy"),2.2);//包里有3个参数
//
// return 0;
//}
int main()
{
ysy::list<ysy::string> lt;
cout << "************以上是哨兵位头节点的构造**************************" << endl;
//传左值,跟push_back一样,走拷贝构造
ysy::string s1("ysy");
lt.emplace_back(s1);
//lt.push_back(s1);
cout << "**************************************************************" << endl;
//右值,跟push_back一样,走移动构造
//lt.emplace_back(move(s1));
lt.push_back(move(s1));
cout << "**************************************************************" << endl;
cout << "***********************下面就是二者不一样的部分了*************" << endl;
//直接把构造string参数包往下传,直接用string参数包构造string
//这里达到的效果是push_back做不到的
lt.emplace_back("sysysy");
lt.push_back("sysysy");
cout << "**************************************************************" << endl;
cout << "* **********************下面就是二者功能重叠的部分 * ************" << endl;
list<pair<ysy::string, int>> lt1;
//跟push_back一样
//构造pair+拷贝/移动构造pair到list的结点中data上
pair<ysy::string, int> kv("苹果", 1);
lt1.emplace_back(kv);
lt1.push_back(kv);
cout << "**************************************************************" << endl;
//跟push_back一样
//lt1.emplace_back(move(kv));
lt1.push_back(move(kv));
cout << "**************************************************************" << endl;
cout << "***********************下面就是二者不一样的部分了*************" << endl;
lt1.emplace_back("草莓",1);
//lt1.emplace_back({ "草莓",1 }); //emplace_back不能这样写,加上{}类型不明确,"草莓",1类型不一样
//lt1.push_back("草莓",1); //push_back不能这样写
lt1.push_back({ "草莓",1 });
cout << "**************************************************************" << endl;
return 0;
}
6. 新的类功能
6.1 默认的移动构造和移动赋值
- 原来C++类中,有6个默认成员函数:构造函数/析构函数/拷贝构造函数/拷贝赋值重载/取地址重载/const 取地址重载,最后重要的是前4个,后两个用处不⼤,默认成员函数就是我们不写编译器会生成⼀个默认的。C++11 新增了两个默认成员函数,移动构造函数和移动赋值运算符重载。
- 移动构造只针对深拷贝相关的系列的有意义,内置类型首先就没有移动构造的概念了,自定义类型纯浅拷贝的类型,移动构造也没有什么意义。所以是针对深拷贝的类型或者是包含深拷贝的类型,自己成员有个深拷贝或者是自己是个深拷贝都是可以的。
- 如果你没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意⼀个(一个类如果显示写了析构函数释放资源,那么就要写拷贝构造和拷贝赋值,三个是一体化的)。 那么编译器会自动生成⼀个默认移动构造。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造。
- 如果你没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意⼀个,那么编译器会自动生成⼀个默认移动赋值。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上⾯移动构造完全类似)
- 如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。
移动构造:
Person没有写移动构造,对内置类型完成值拷贝

对于自定义类型调用它的拷贝构造,完成深拷贝


Person没有写移动构造,符合会默认生成移动构造的条件,默认生成移动构造对内置类型完成值拷贝,对自定义类型看是否实现移动构造,有就调用移动构造,没有就调用拷贝构造,string实现了移动构造:


写了析构的情况下:
cpp
class Person
{
public:
Person(const char* name = "ysy",int age=0)
:_name(name)
,_age(age)
{ }
//写了析构
~Person()
{ }
private:
ysy::string _name;
//Person不是深拷贝的类型,但是包含深拷贝的类型ysy::string _name;
int _age;
};
写了析构,没有生成默认的拷贝构造,其他地方就都不会改变,无论是左值还是右值都调用拷贝构造:

移动赋值:



6.2 成员变量声明时给缺省值
成员变量声明时给缺省值是给初始化列表用的,如果没有显示在初始化列表初始化,就会在初始化列表用这个缺省值初始化,这个我们在类和对象部分中提过,这里就不再描述了。
6.3 defult 和 delete
- C++11可以让你更好的控制要使用的默认函数。假设你要使用某个默认的函数,但是因为⼀些原因这个函数没有默认生成。比如:我们提供了拷⻉构造,就不会生成移动构造了,那么我们可以使用default关键字显⽰指定移动构造生成。
- 如果能想要限制某些默认函数的生成,在C++98中,是该函数设置成private,并且只声明补丁已,这样只要其他人想要调用就会报错。在C++11中更简单,只需在该函数声明加上=delete即可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数。
cpp
class Person
{
public:
Person(const char* name = "", int age = 0)
:_name(name)
, _age(age)
{
}
Person(const Person& p)
:_name(p._name)
, _age(p._age)
{
}
Person(Person&& p) = default;
//Person(const Person& p) = delete;
private:
ysy::string _name;
int _age;
};
int main()
{
Person s1;
Person s2 = s1;
Person s3 = std::move(s1);
return 0;
}
6.4 final 与 override
之前在继承和多态的文章中描述过,不再描述。
7. STL中一些变化
- 第一个方面就是新容器,C++11给出了好几个新容器,最有用的就是unordered_map和unordered_set。
- 所有的容器都增了新接口,新接口最重要的就是右值引用和移动语义相关的push/insert/emplace系列,push/insert/emplace系列在提高效率,是刚需。还有initializer_list版本的构造等,这个就不是刚需了。
- 容器的范围for遍历,会很方便。

实现的才不会去调用算法库中的swap,但是没有实现的就要去调用算法库中的swap,效率低。
C++11对这一情况进行了改善因为有右值引用的移动语义

有了C++11这个右值引用之前的swap版本都可以不用了,都走C++11的swap版本就行了。
8.包装器
包装器也是引入了一些新语法搞出来的东西。现在我们学到的可调用对象有这样的几类:函数指针(能不用就不用,类型定义太复杂,例如:void(*p)(int))、仿函数、lambda,包装器(有两类,一类是function、一类是bind)
8.1 function
cpp
template <class T>
class function; // undefined
template <class Ret, class... Args>
class function<Ret(Args...)>;
function是一个类模板,是一个特殊的仿函数,里面是可以重载operator(),可以像函数那样去使用

实际实现的是特化的类

模板参数是上面定义的可调用对象的<返回类型(参数)>,特化的时候就会调用到上面的类,这样规定是为了更像函数一点
特点是:可以包装各种各样的可调用类型,对函数指针、仿函数、lambda进行进一步包装,好处是可以包装成统一类型。但是在基于返回值和参数类型一样的情况下才能够进行统一,否则,统一不了
包装器构造的核心是:可以传任意类型的可以调用类型的对象给包装器,可以构造空的,也可以用空指针去构造。

在构造的时候是可以包装各种可调用的对象
cpp
int f(int a, int b)
{
return a + b;
}
struct Functor
{
public:
int operator()(int a, int b)
{
return a + b;
}
};
class Plus
{
public:
Plus(int n = 10)
:_n(n)
{ }
static int plusi(int a, int b)
{
return a + b;
}
private:
int _n;
};
int main()
{
//包装各种可调用的对象
function<int(int, int)>f1 = f; //包装普通的函数指针
function<int(int, int)>f2 = Functor(); //仿函数
function<int(int, int)>f3 = [](int a, int b) {return a + b; }; //lambda
cout << f1(1, 1) << endl;
cout << f2(1, 1) << endl;
cout << f3(1, 1) << endl;
return 0;
}
运行结果:

包装成员函数指针,有些不一样的地方,function是基于有可变模板参数,此时才能支持function,包装静态成员函数指针跟普通函数指针一样包就行了。
cpp
int f(int a, int b)
{
return a + b;
}
struct Functor
{
public:
int operator()(int a, int b)
{
return a + b;
}
};
class Plus
{
public:
Plus(int n = 10)
:_n(n)
{ }
static int plusi(int a, int b)
{
return a + b;
}
double plusd(double a, double b)
{
return (a + b) * _n;
}
private:
int _n;
};
int main()
{
//包装静态成员函数
//成员函数要指定类域并且前面加&才能获取地址
function<int(int, int)>f4 = &Plus::plusi;
cout << f4(1, 1) << endl;
return 0;
}
cpp
int f(int a, int b)
{
return a + b;
}
struct Functor
{
public:
int operator()(int a, int b)
{
return a + b;
}
};
class Plus
{
public:
Plus(int n = 10)
:_n(n)
{ }
static int plusi(int a, int b)
{
return a + b;
}
double plusd(double a, double b)
{
return (a + b) * _n;
}
private:
int _n;
};
int main()
{
//包装成员函数指针
function<double(Plus*,double, double)>f5 = &Plus::plusd;
Plus ps;
cout << f5(&ps, 1.1, 1.1) << endl;
return 0;
}
运行结果:

包装成员函数的指针要小心,成员函数指针有一个隐藏的参数,就是this指针。
包装成员函数指针不仅仅可以向上面那样写(传指针),也可以下面那样写(传对象)
cpp
function<double(Plus, double, double)>f6 = &Plus::plusd;
cout << f6(Plus(), 1.1, 1.1) << endl;
底层并不是将对象传给这个函数了,底层包装了之后,调用operator(),会调用这个函数,时会将对象的地址是不能直接传过去的,因为this指针是不能显示传递的,无论是传指针还是传对象,底层还是用这个指针或是对象去调用这个函数指针plusd
也可以这样写,右值引用版本:
cpp
function<double(Plus&&, double, double)>f7 = &Plus::plusd;
cout << f7(Plus(), 1.1, 1.1) << endl;
8.2 bind(绑定)
cpp
simple(1)
template <class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);
with return type (2)
template <class Ret, class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);
对可调用对象的一个绑定,function是一个类模板,bind是一个函数模板,它也是⼀个可调用对象的包装器,返回一个可调用对象的类型,function是统一类型,可以包装任意类型然后进行调用,bind是用来调整,bind是可以用来调整参数个数和参数顺序的。bind也<functional>这个头文件中。
调⽤bind的⼀般形式: auto newCallable = bind(callable,arg_list); 其中newCallable本⾝是⼀个可调用对象,arg_list是⼀个逗号分隔的参数列表,对应给定的callable的参数。当我们调用newCallable时,newCallable会调用callable,并传给它arg_list中的参数。
arg_list中的参数可能包含形如_n的名字,其中n是⼀个整数,这些参数是占位符,表示newCallable的参数,它们占据了传递给newCallable的参数的位置。数值n表示⽣成的可调⽤对象中参数的位置:_1为newCallable的第⼀个参数,_2为第⼆个参数,以此类推。_1/_2/_3....这些占位符放到placeholders的⼀个命名空间中。

bind引出另一个东西:placeholders,placeholders是一个占位符,placeholders是一个命名空间,命名空间里面是定义了一堆_1,_2,_3这样的一堆占位符,是参数的占位符。

cpp
using placeholders::_1;
using placeholders::_2;
using placeholders::_3;
int Sub(int a, int b)
{
return (a - b) * 10;
}
int SubX(int a, int b, int c)
{
return (a - b - c) * 10;
}
class Plus
{
public:
static int plusi(int a, int b)
{
return a + b;
}
double plusd(double a, double b)
{
return a + b;
}
};
int main()
{
//调整参数顺序------很少使用
auto sub1 = bind(Sub, _1, _2);
cout << sub1(10, 5) << endl;
auto sub2 = bind(Sub, _2, _1);
cout << sub2(10, 5) << endl;
return 0;
}
运行结果:

_1永远代表都是第一个参数,_2永远代表的是第二个参数,这样就可以达到调整参数顺序的目的。

bind最常用的是将有些参数绑死
cpp
//调整参数个数
auto sub3 = bind(Sub, 100, _1);
cout << sub3(5) << endl;
运行结果:

cpp
//调整参数个数
//绑死b
auto sub4 = bind(Sub, _1,100);
cout << sub4(5) << endl;
运行结果:


cpp
//分别绑死第123个参数
auto sub5 = bind(SubX, 100, _1, _2);
cout << sub5(5,1) << endl;
auto sub6 = bind(SubX, _1, 100, _2);
cout << sub6(5, 1) << endl;
auto sub7 = bind(SubX, _1, _2, 100);
cout << sub7(5, 1) << endl;

cpp
int main()
{
// 计算复利的lambda
auto func1 = [](double rate, double money, int year)->double {
double ret = money;
for (int i = 0; i < year; i++)
{
ret += ret * rate;
}
return ret - money;
};
cout << func1(0.02, 100000, 30) << endl;
// 绑死一些参数,实现出支持不同年华利率,不同金额和不同年份计算出复利的结算利息
function<double(double)> func3_1_5 = bind(func1, 0.015, _1, 3);
function<double(double)> func5_1_5 = bind(func1, 0.015, _1, 5);
function<double(double)> func10_2_5 = bind(func1, 0.025, _1, 10);
function<double(double)> func30_3_5 = bind(func1, 0.035, _1, 30);
cout << func3_1_5(1000000) << endl;
cout << func5_1_5(1000000) << endl;
cout << func10_2_5(1000000) << endl;
cout << func30_3_5(1000000) << endl;
return 0;
}
运行结果:
