目录
一、新的类功能
1.1 默认成员函数
在之前的学习过程中,我们已经知道了6个默认成员函数,分别是:
- 构造函数
- 析构函数
- 拷贝构造
- 拷贝赋值重载
- 取地址重载
- const 取地址重载
默认成员函数的特点:我们不写,编译器会生成出来一个默认的
C++11新增了两个:
- 移动构造函数
- 移动赋值运算符重载
1.1.1 移动构造函数
默认移动构造函数出现的条件:
- 自己没有实现移动构造函数
- 并且自己没有实现析构、拷贝构造和拷贝赋值重载函数中的任意一个
- 默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造
class Person
{
public:
Person(const char* name = "", int age = 0)
:_name(name)
, _age(age)
{}
/*~Person()
{}*/
private:
yss::string _name;
int _age;
};
int main()
{
Person s1;
Person s2 = std::move(s1);//右值
return 0;
}
取消注释后运行:
~Person()
{}
1.1.2 移动赋值运算符重载
默认移动赋值运算符重载出现的条件:
- 自己没有实现移动赋值运算符重载
- 并且自己没有实现析构、拷贝构造和拷贝赋值重载函数中的任意一个
- 默认生成的移动赋值运算符重载函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋值重载,如果实现了就调用移动赋值重载函数,没有实现就调用拷贝赋值
class Person
{
public:
Person(const char* name = "", int age = 0)
:_name(name)
, _age(age)
{}
//~Person()
//{}
private:
yss::string _name;
int _age;
};
int main()
{
Person s1;
Person s2;
s2 = std::move(s1);
return 0;
}
取消注释后运行:
~Person()
{}
1.2 关键字default
作用:强制生成默认函数。 如果我们有写构造函数,就不会生成移动构造,可以使用default关键字显示指定移动构造生成。
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;
private:
yss::string _name;
int _age;
};
1.3 关键字delete
作用:禁止生成默认函数。 只需在该函数声明加上=delete即可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数。
二、可变参数模板
2.1 可变参数的函数模板
以前学习的是类模板和函数模板,这两个的特点是只能含固定数量的模版参数。可变参数模板在此基础上有了很大的提升,可以接收0到N个模板参数
下面就是一个基本可变参数的函数模板:
template <class ...Args>
void ShowList(Args... args)
{}
- Args是一个模板参数包,args是一个函数形参参数包
- 声明一个参数包Args…args,这个参数包中可以包含0到任意个模板参数
2.2 递归方式展开函数
先看看显示函数参数包中的参数个数:
template <class ...Args>
void ShowList(Args... args)
{
cout << sizeof...(args) << endl;
}
int main()
{
ShowList();//0
ShowList(1);//1
ShowList(1, 'A');//2
ShowList(1, 'A', std::string("sort"));//3
return 0;
}
如果想打印函数参数包中的每个参数呢,使用for循环?
很明显是不行的,因为模板是编译时解析,for循环是运行时解析参数。
方法:编译时递归解析
//递归终止条件
void _ShowList()
{
cout << endl;
}
//子函数中递归
template <class T, class ...Args>
void _ShowList(const T& val, Args... args)
{
cout << val << " ";
_ShowList(args...);
}
//传入函数参数包
template <class ...Args>
void ShowList(Args... args)
{
_ShowList(args...);
}
int main()
{
ShowList(1);
ShowList(1, 'A');
ShowList(1, 'A', std::string("sort"));
return 0;
}
分析:以参数为3个为例
2.3 empalce
empalce系列中的emplace_back函数与push_back函数的功能是相同的,都是尾插数据。但是C++11新增empalce函数系列支持模板的可变参数和万能引用。下面的是emplace_back函数:
通过以下代码的对比,区分出empalce函数的优势在哪:
1️⃣深拷贝类对象
int main()
{
list<yss::string> lt1;
yss::string s1("xxxx");
lt1.push_back(s1);//左值
lt1.push_back(move(s1));//右值
cout << endl;
yss::string s2("xxxx");
lt1.emplace_back(s2);//左值
lt1.emplace_back(move(s2));//右值
cout << endl;
lt1.push_back("1111");//右值
lt1.emplace_back("1111");//右值
return 0;
}
对于深拷贝类对象,如果插入的是类对象时,两个没有区别;如果插入的是对象的参数,那么emplace系列函数会减少一次移动构造。
2️⃣浅拷贝类对象
int main()
{
list<Date> lt1;
Date d1(1, 1, 1);
lt1.push_back(d1);//左值
lt1.push_back(move(d1));//右值
cout << endl;
Date d2(2, 2, 2);
lt1.emplace_back(d2);//左值
lt1.emplace_back(move(d2));//右值
cout << endl;
lt1.push_back({ 3, 3, 3 });//构造+拷贝构造
lt1.emplace_back(3, 3, 3);//构造
return 0;
}
注意:emplace_back不支持使用列表初始化
对于浅拷贝类对象,如果插入的是类对象时,两个没有区别;如果插入的是对象的参数,那么emplace系列函数会减少一次拷贝构造。
总结一下(以emplace_back为代表):
- emplace_back和push_back都可以实现尾插,但是emplace_back的参数不仅可以接收左值和右值,还可以接收参数包
- 直接插入对象参数的情况下,如果是浅拷贝类对象,减少了一次拷贝构造;如果是深拷贝类对象,减少了一次移动构造
- 综合前面两点,emplace系列函数在性能上更加优越