文章目录
一、新的类功能
原来C++类中,有6个默认成员函数:
- 构造函数、析构函数、拷贝构造函数、拷贝赋值函数、取地址重载、cosnt取地址重载
前4个比较重要,后面两个默认成员函数一般不会用到
但是C++11新增加了两个默认成员函数:移动构造函数和移动赋值运算符重载
移动构造函数和移动赋值运算符重载的生成:
如果没有自己实现移动构造函数:并且没有实现析构函数、拷贝构造和拷贝赋值重载中的任意一个,那么编译器就会自动生成一个默认移动构造函数。
默认生成的移动构造函数
,对于内置类型成员会完成值拷贝(浅拷贝),对于自定义类型成员,如果实现了移动构造,就调用移动构造,没有实现就调用拷贝构造如果没有自己实现移动赋值运算符重载:并且没有实现析构函数、卡搜被构造、拷贝赋值重载中的任意一个,那么编译器就会自动生成一个默认移动赋值。
默认生成的移动构造函数
,对于内置类型完成值拷贝(浅拷贝),对于自定义类型成员,如果实现了移动赋值,就调用移动赋值,没有实现就调用拷贝构造(默认移动赋值与上面的移动构造类似)最后,如果提供了移动构造和移动赋值,编译器就不会自动提供拷贝构造和拷贝赋值
- 默认生成的移动构造与默认生成的移动赋值运算符
//默认生成移动构造
class Person
{
public:
Person(const char*name="",int age = 0)
:_name(name)
,_age(age)
{}
private:
hwc::string _name;//调用hwc中的string的移动构造与移动赋值
int _age;//值拷贝
};
int main()
{
Person s1;
Person s2 = s1;//左值,拷贝构造。
Person s3 = std::move(s1);//右值,移动构造
Person s4;
s4 = std::move(s2);//移动赋值
return 0;
}
二、类成员变量初始化
C++11允许在类定义时给成员变量初始缺省值,默认生成构造函数会使用这些缺省值初始化,这个我们在类和对象就有说过了。
1.强制生成默认函数的关键字default
C++11可以更好的控制使用的默认函数。当我们要使用某个默认的函数,但是因为一些原因这个函数没有默认生成:比如我们提供了析构函数,就不会有移动构造默认生成了。此时我们就可以使用default这个关键字来显示指定移动构造的生成
class Person
{
public:
Person(const char*name="",int age = 0)
:_name(name)
,_age(age)
{}
/*
自己实现移动构造比较麻烦
Person(Person&&p)
:_name(std::forward<hwc::string>(p._name))
,_age(p._age)
{}
*/
//移动构造强制生成
Person(Person&& p) = default;
//拷贝构造
Person(const Person&p)
:_name(p._name)
,_age(p._age)
{}
~Person()
{}
private:
hwc::string _name;//调用hwc中的string的移动构造与移动赋值
int _age;//值拷贝
};
int main()
{
Person s1;
Person s2 = s1;//左值,拷贝构造。
Person s3 = std::move(s1);//右值,移动构造
return 0;
}
2.禁止生成默认函数的关键字delete
在C++98中,如果要限制某些函数的生成,把该函数设置成private,并且只声明不实现
//不想让A类对象拷贝 class A { public: void func() { A tmp(*this); } A() {} ~A() { delete[] p; } private: //只声明不实现,声明为私有 A(const A& aa); private: int* p = new int[10]; };
在C++11中,只需要在该函数声明上加上=delete即可,该语法指示编译器不生成对应函数的默认版本,=delete修饰的函数为删除函数
//不想让A类对象拷贝 class A { public: void func() { A tmp(*this); } A() {} ~A() { delete[] p; } A(const A& aa) = delete; private: int* p = new int[10]; }; int main() { A aa1; aa1.func(); //A aa2(aa1); return 0; }
三、可变参数模板
可变参数模板是C++11新增的特性之一,能够让我们创建可以接收可变参数的函数模板和类模板
1.可变参数的函数模板
可变参数模板定义:
template<class ...Args>
void ShowList(Args... args)
{
}
Args是一个模板参数包
,args是一个函数形参参数包
,声明一个参数包Arag…args,这个参数包中可以包含0到任意个模板参数
template<class ...Args>
void ShowList(Args... args)
{
}
int main()
{
ShowList();
ShowList(1);
ShowList(1, 1.2);
ShowList(1, 1.3, string("xxxxx"));
return 0;
}
可以通过sizeof算出参数包中参数的个数,计算的是参数个数:...
是在外面的
template<class ...Args>
void ShowList(Args... args)
{
cout << sizeof...(args) << endl;
}
如何获取参数包中的每个参数?
我们以前都是习惯[],但是这里语法并不支持使用 args[i]
的方式来获取参数包中的参数,只能通过展开参数包的方式来获取,这是使用可变参数模板的一个主要特点
下面是错误示范:
template<class ...Args>
void ShowList(Args... args)
{
cout << sizeof...(args) << endl;
for (int i = 0; i < sizeof...(args); i++)
{
cout << args[i] << " ";
}
cout << endl;
}
但是C++并不支持这种方法
2.参数包的展开
- 递归展开
函数递归方式展开:
先给可变参数的函数模板增加一个模板参数class T,从接收的参数包中把第一个参数分离出来
在函数模板中递归调用该函数模板,调用时传入的剩下的参数包
直到递归到参数包为空,退出递归。
void ShowList()
{
cout << endl;
}
template<class T,class ...Args>
void ShowList(T val,Args... args)
{
cout << val << " ";
ShowList(args...);
}
- 逗号表达式展开
逗号表达式是会从左到右依次计算各个表达式,并将最后一个表达式的值作为返回值返回:我们将最后一个表达式设为整型值,所以最后返回的是一个整型;将处理参数个数的动作封装成一个函数,将该函数作为逗号表达式的第一个表达式;…代表参数包,列表展开;另外,我们要的是打印出参数包中的各个参数,因此处理函数PrintArg当中要做的就是将传入的参数进行打印即可:
template <class T>
void PrintArg(T t)
{
cout << t << " ";
}
template<class ...Args>
void ShowList(Args... args)
{
int arr[] = { (PrintArg(args),0)... };
cout << endl;
}
int main()
{
ShowList(1);
ShowList(1, 1.2);
ShowList(1, 1.3, string("xxxxx"));
return 0;
}
我们也可以这样子写,不用逗号表达式:直接展开参数包初始化数组
template <class T>
int PrintArg(T t)
{
cout << t << " ";
return 0;
}
template<class ...Args>
void ShowList(Args... args)
{
int arr[] = { PrintArg(args)... };
cout << endl;
}
int main()
{
ShowList(1);
ShowList(1, 1.2);
ShowList(1, 1.3, string("xxxxx"));
return 0;
}
四、emplace
C++11给STL容器新增加了emplace的相关接口,比如list容器的push_front、push_back、insert都有了对应的emplace_front、emplace_back、emplace:
这些emplace
相关的接口也支持了模板的可变参数,比如vector容器的emplac函数的声明如下:
1.使用
push_back与emlace_back对于内置类型并没有什么区别,emplace_back 插入元素时,也可以传入左值对象或右值对象,但不可以使用列表进行初始化,而push_back是可以使用列表初始化的:
int main()
{
list<int> list1;
list1.push_back(1);
list1.emplace_back(2);
list1.emplace_back();
for (auto e : list1)
{
cout << e << " ";
}
cout << endl;
return 0;
}
emplace
系列接口最大的特点就是,插入元素可传入用于构造元素的参数包
int main()
{
std::list<std::pair<int, char >> mylist;
mylist.push_back(make_pair(1, 'a'));//构造+拷贝构造
//mylist.push_back(1,'a');错误,不支持
mylist.emplace_back(1, 'a');//直接构造
return 0;
}
2.意义
emplace接口的可变参数模板是万能引用,既可以接收左值,也可以接收右值,同时还可以接收参数包
如果调用emplace接口是传入的参数是参数包,那就可以调用行函数进行插入,最终定位new表达式调用构造函数对空间进行初始化,匹配到
构造函数
调用 emplace 接口时传入的是左值或者右值,首先需要先在此之前调用构造函数实例化出一个对象,最后使用定位 new 表达式调用构造函数对空间进行初始化时,会匹配到拷贝构造或者移动构造
也就是说,如果传入的是参数包,只会调用构造函数
class Date
{
public:
Date(int year=1, int month=1, int day=1)
:_year(year)
, _month(month)
, _day(_day)
{
cout << "构造" << endl;
}
Date(const Date& d)
:_year(d._year)
,_month(d._month)
,_day(d._day)
{
cout << "拷贝构造" << endl;
}
void swap(Date& d)
{
std::swap(_year, d._year);
std::swap(_month, d._month);
std::swap(_day, d._day);
}
Date(Date&& d)
{
cout << "移动构造" << endl;
swap(d);
}
private:
int _year;
int _month;
int _day;
};
int main()
{
list<Date> lt1;
Date d1(2, 2, 2);
lt1.emplace_back(d1);//左值
cout << "----------------------------------" << endl;
lt1.emplace_back(20, 20, 20);//参数包
cout << endl;
Date d2(3, 3, 3);
lt1.push_back(d2);//左值
cout << "----------------------------------" << endl;
lt1.push_back(Date(1, 1, 1));//右值
cout << "----------------------------------" << endl;
lt1.push_back({ 3,3,3 });//右值
return 0;
}
emplace 最大特点就是支持传入参数包,用这些参数包直接构造出对象,这样就能减少一次拷贝,这就是为什么有人说 emplace 系列接口更高效的原因。