目录
- 一、新的类功能
- 1.1 默认成员函数
- 1.1.1 移动构造函数
- 1.1.2 移动赋值运算符重载
- 1.2 关键字default
- 1.3 关键字delete
- 二、可变参数模板
- 2.1 可变参数的函数模板
- 2.2 递归方式展开函数
- 2.3 empalce
一、新的类功能
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();//0ShowList(1);//1ShowList(1, 'A');//2ShowList(1, 'A', std::string("sort"));//3return 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系列函数在性能上更加优越